From 87c7ce254a4cf318d9846938138847ecec15c320 Mon Sep 17 00:00:00 2001
From: Vishal Choudhary
Date: Tue, 23 Jan 2024 17:57:39 +0530
Subject: [PATCH] feat: add skipImageReferences in verify images (#8633)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* feat: add skipImageReferences in verify images
Signed-off-by: Vishal Choudhary
* fix: chainsaw tests
Signed-off-by: Vishal Choudhary
* fix: chainsaw-test.yaml
Signed-off-by: Vishal Choudhary
* fix: typo in assert
Signed-off-by: Vishal Choudhary
---------
Signed-off-by: Vishal Choudhary
Co-authored-by: Charles-Edouard Brétéché
---
api/kyverno/v1/image_verification_types.go | 7 +
api/kyverno/v1/zz_generated.deepcopy.go | 5 +
.../v2beta1/image_verification_types.go | 7 +
api/kyverno/v2beta1/zz_generated.deepcopy.go | 5 +
.../kyverno/charts/crds/templates/crds.yaml | 88 ++++++++++++
.../data/crds/kyverno.io_clusterpolicies.yaml | 44 ++++++
.../data/crds/kyverno.io_policies.yaml | 44 ++++++
config/crds/kyverno.io_clusterpolicies.yaml | 44 ++++++
config/crds/kyverno.io_policies.yaml | 44 ++++++
config/install-latest-testing.yaml | 88 ++++++++++++
docs/user/crd/index.html | 28 ++++
.../kyverno/v1/imageverification.go | 11 ++
.../kyverno/v2beta1/imageverification.go | 11 ++
pkg/engine/api/imageverifymetadata.go | 27 +++-
pkg/engine/api/imageverifymetadata_test.go | 108 +++++++--------
pkg/engine/api/ruleresponse.go | 24 +++-
.../handlers/validation/validate_image.go | 35 +++--
pkg/engine/image_verify_test.go | 125 +++++++++++++++++-
pkg/engine/internal/imageverifier.go | 37 +++++-
pkg/engine/utils/image.go | 13 +-
pkg/webhooks/utils/warning.go | 2 +-
.../chainsaw-step-02-assert-1.yaml | 2 +-
.../chainsaw-step-02-assert-1.yaml | 2 +-
.../chainsaw-step-02-assert-1.yaml | 2 +-
.../chainsaw-step-02-assert-1.yaml | 2 +-
.../chainsaw-step-02-assert-1.yaml | 2 +-
.../chainsaw-step-02-assert-1.yaml | 2 +-
.../chainsaw-step-02-assert-1.yaml | 2 +-
.../standard/skip-image-reference/README.md | 12 ++
.../standard/skip-image-reference/bad.yaml | 19 +++
.../skip-image-reference/chainsaw-test.yaml | 34 +++++
.../skip-image-reference/pod-assert.yaml | 5 +
.../standard/skip-image-reference/pod.yaml | 16 +++
.../skip-image-reference/policy-ready.yaml | 9 ++
.../standard/skip-image-reference/policy.yaml | 66 +++++++++
.../skip-image-reference/skipped-assert.yaml | 5 +
.../skip-image-reference/skipped.yaml | 16 +++
37 files changed, 899 insertions(+), 94 deletions(-)
create mode 100644 test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/README.md
create mode 100644 test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/bad.yaml
create mode 100644 test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/chainsaw-test.yaml
create mode 100644 test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/pod-assert.yaml
create mode 100644 test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/pod.yaml
create mode 100644 test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/policy-ready.yaml
create mode 100755 test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/policy.yaml
create mode 100644 test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/skipped-assert.yaml
create mode 100644 test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/skipped.yaml
diff --git a/api/kyverno/v1/image_verification_types.go b/api/kyverno/v1/image_verification_types.go
index 3ad5a49c09..40b4f5f974 100644
--- a/api/kyverno/v1/image_verification_types.go
+++ b/api/kyverno/v1/image_verification_types.go
@@ -56,6 +56,13 @@ type ImageVerification struct {
// +kubebuilder:validation:Optional
ImageReferences []string `json:"imageReferences,omitempty" yaml:"imageReferences,omitempty"`
+ // SkipImageReferences is a list of matching image reference patterns that should be skipped.
+ // At least one pattern in the list must match the image for the rule to be skipped. Each image reference
+ // consists of a registry address (defaults to docker.io), repository, image, and tag (defaults to latest).
+ // Wildcards ('*' and '?') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.
+ // +kubebuilder:validation:Optional
+ SkipImageReferences []string `json:"skipImageReferences,omitempty" yaml:"skipImageReferences,omitempty"`
+
// Deprecated. Use StaticKeyAttestor instead.
Key string `json:"key,omitempty" yaml:"key,omitempty"`
diff --git a/api/kyverno/v1/zz_generated.deepcopy.go b/api/kyverno/v1/zz_generated.deepcopy.go
index df925f686f..90f8f017c6 100755
--- a/api/kyverno/v1/zz_generated.deepcopy.go
+++ b/api/kyverno/v1/zz_generated.deepcopy.go
@@ -758,6 +758,11 @@ func (in *ImageVerification) DeepCopyInto(out *ImageVerification) {
*out = make([]string, len(*in))
copy(*out, *in)
}
+ if in.SkipImageReferences != nil {
+ in, out := &in.SkipImageReferences, &out.SkipImageReferences
+ *out = make([]string, len(*in))
+ copy(*out, *in)
+ }
if in.AdditionalExtensions != nil {
in, out := &in.AdditionalExtensions, &out.AdditionalExtensions
*out = make(map[string]string, len(*in))
diff --git a/api/kyverno/v2beta1/image_verification_types.go b/api/kyverno/v2beta1/image_verification_types.go
index 04efac4a3b..5ec162086d 100644
--- a/api/kyverno/v2beta1/image_verification_types.go
+++ b/api/kyverno/v2beta1/image_verification_types.go
@@ -21,6 +21,13 @@ type ImageVerification struct {
// +kubebuilder:validation:Optional
ImageReferences []string `json:"imageReferences,omitempty" yaml:"imageReferences,omitempty"`
+ // SkipImageReferences is a list of matching image reference patterns that should be skipped.
+ // At least one pattern in the list must match the image for the rule to be skipped. Each image reference
+ // consists of a registry address (defaults to docker.io), repository, image, and tag (defaults to latest).
+ // Wildcards ('*' and '?') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.
+ // +kubebuilder:validation:Optional
+ SkipImageReferences []string `json:"skipImageReferences,omitempty" yaml:"skipImageReferences,omitempty"`
+
// Attestors specified the required attestors (i.e. authorities)
// +kubebuilder:validation:Optional
Attestors []kyvernov1.AttestorSet `json:"attestors,omitempty" yaml:"attestors,omitempty"`
diff --git a/api/kyverno/v2beta1/zz_generated.deepcopy.go b/api/kyverno/v2beta1/zz_generated.deepcopy.go
index 33f36147cb..b3326b74eb 100755
--- a/api/kyverno/v2beta1/zz_generated.deepcopy.go
+++ b/api/kyverno/v2beta1/zz_generated.deepcopy.go
@@ -376,6 +376,11 @@ func (in *ImageVerification) DeepCopyInto(out *ImageVerification) {
*out = make([]string, len(*in))
copy(*out, *in)
}
+ if in.SkipImageReferences != nil {
+ in, out := &in.SkipImageReferences, &out.SkipImageReferences
+ *out = make([]string, len(*in))
+ copy(*out, *in)
+ }
if in.Attestors != nil {
in, out := &in.Attestors, &out.Attestors
*out = make([]v1.AttestorSet, len(*in))
diff --git a/charts/kyverno/charts/crds/templates/crds.yaml b/charts/kyverno/charts/crds/templates/crds.yaml
index 258b710b5f..a019949f73 100644
--- a/charts/kyverno/charts/crds/templates/crds.yaml
+++ b/charts/kyverno/charts/crds/templates/crds.yaml
@@ -14291,6 +14291,17 @@ spec:
roots:
description: Deprecated. Use KeylessAttestor instead.
type: string
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped. At
+ least one pattern in the list must match the image for
+ the rule to be skipped. Each image reference consists
+ of a registry address (defaults to docker.io), repository,
+ image, and tag (defaults to latest). Wildcards (''*''
+ and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
subject:
description: Deprecated. Use KeylessAttestor instead.
type: string
@@ -18849,6 +18860,17 @@ spec:
roots:
description: Deprecated. Use KeylessAttestor instead.
type: string
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped.
+ At least one pattern in the list must match the
+ image for the rule to be skipped. Each image reference
+ consists of a registry address (defaults to docker.io),
+ repository, image, and tag (defaults to latest).
+ Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
subject:
description: Deprecated. Use KeylessAttestor instead.
type: string
@@ -23048,6 +23070,17 @@ spec:
i.e. have matched passed a signature or attestation
check.
type: boolean
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped. At
+ least one pattern in the list must match the image for
+ the rule to be skipped. Each image reference consists
+ of a registry address (defaults to docker.io), repository,
+ image, and tag (defaults to latest). Wildcards (''*''
+ and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
type:
description: Type specifies the method of signature validation.
The allowed options are Cosign and Notary. By default
@@ -27603,6 +27636,17 @@ spec:
roots:
description: Deprecated. Use KeylessAttestor instead.
type: string
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped.
+ At least one pattern in the list must match the
+ image for the rule to be skipped. Each image reference
+ consists of a registry address (defaults to docker.io),
+ repository, image, and tag (defaults to latest).
+ Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
subject:
description: Deprecated. Use KeylessAttestor instead.
type: string
@@ -32099,6 +32143,17 @@ spec:
roots:
description: Deprecated. Use KeylessAttestor instead.
type: string
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped. At
+ least one pattern in the list must match the image for
+ the rule to be skipped. Each image reference consists
+ of a registry address (defaults to docker.io), repository,
+ image, and tag (defaults to latest). Wildcards (''*''
+ and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
subject:
description: Deprecated. Use KeylessAttestor instead.
type: string
@@ -36658,6 +36713,17 @@ spec:
roots:
description: Deprecated. Use KeylessAttestor instead.
type: string
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped.
+ At least one pattern in the list must match the
+ image for the rule to be skipped. Each image reference
+ consists of a registry address (defaults to docker.io),
+ repository, image, and tag (defaults to latest).
+ Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
subject:
description: Deprecated. Use KeylessAttestor instead.
type: string
@@ -40858,6 +40924,17 @@ spec:
i.e. have matched passed a signature or attestation
check.
type: boolean
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped. At
+ least one pattern in the list must match the image for
+ the rule to be skipped. Each image reference consists
+ of a registry address (defaults to docker.io), repository,
+ image, and tag (defaults to latest). Wildcards (''*''
+ and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
type:
description: Type specifies the method of signature validation.
The allowed options are Cosign and Notary. By default
@@ -45413,6 +45490,17 @@ spec:
roots:
description: Deprecated. Use KeylessAttestor instead.
type: string
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped.
+ At least one pattern in the list must match the
+ image for the rule to be skipped. Each image reference
+ consists of a registry address (defaults to docker.io),
+ repository, image, and tag (defaults to latest).
+ Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
subject:
description: Deprecated. Use KeylessAttestor instead.
type: string
diff --git a/cmd/cli/kubectl-kyverno/data/crds/kyverno.io_clusterpolicies.yaml b/cmd/cli/kubectl-kyverno/data/crds/kyverno.io_clusterpolicies.yaml
index 5b7de6c526..e001acdffd 100644
--- a/cmd/cli/kubectl-kyverno/data/crds/kyverno.io_clusterpolicies.yaml
+++ b/cmd/cli/kubectl-kyverno/data/crds/kyverno.io_clusterpolicies.yaml
@@ -4340,6 +4340,17 @@ spec:
roots:
description: Deprecated. Use KeylessAttestor instead.
type: string
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped. At
+ least one pattern in the list must match the image for
+ the rule to be skipped. Each image reference consists
+ of a registry address (defaults to docker.io), repository,
+ image, and tag (defaults to latest). Wildcards (''*''
+ and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
subject:
description: Deprecated. Use KeylessAttestor instead.
type: string
@@ -8898,6 +8909,17 @@ spec:
roots:
description: Deprecated. Use KeylessAttestor instead.
type: string
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped.
+ At least one pattern in the list must match the
+ image for the rule to be skipped. Each image reference
+ consists of a registry address (defaults to docker.io),
+ repository, image, and tag (defaults to latest).
+ Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
subject:
description: Deprecated. Use KeylessAttestor instead.
type: string
@@ -13097,6 +13119,17 @@ spec:
i.e. have matched passed a signature or attestation
check.
type: boolean
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped. At
+ least one pattern in the list must match the image for
+ the rule to be skipped. Each image reference consists
+ of a registry address (defaults to docker.io), repository,
+ image, and tag (defaults to latest). Wildcards (''*''
+ and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
type:
description: Type specifies the method of signature validation.
The allowed options are Cosign and Notary. By default
@@ -17652,6 +17685,17 @@ spec:
roots:
description: Deprecated. Use KeylessAttestor instead.
type: string
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped.
+ At least one pattern in the list must match the
+ image for the rule to be skipped. Each image reference
+ consists of a registry address (defaults to docker.io),
+ repository, image, and tag (defaults to latest).
+ Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
subject:
description: Deprecated. Use KeylessAttestor instead.
type: string
diff --git a/cmd/cli/kubectl-kyverno/data/crds/kyverno.io_policies.yaml b/cmd/cli/kubectl-kyverno/data/crds/kyverno.io_policies.yaml
index 794b75d934..47f889a79e 100644
--- a/cmd/cli/kubectl-kyverno/data/crds/kyverno.io_policies.yaml
+++ b/cmd/cli/kubectl-kyverno/data/crds/kyverno.io_policies.yaml
@@ -4341,6 +4341,17 @@ spec:
roots:
description: Deprecated. Use KeylessAttestor instead.
type: string
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped. At
+ least one pattern in the list must match the image for
+ the rule to be skipped. Each image reference consists
+ of a registry address (defaults to docker.io), repository,
+ image, and tag (defaults to latest). Wildcards (''*''
+ and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
subject:
description: Deprecated. Use KeylessAttestor instead.
type: string
@@ -8900,6 +8911,17 @@ spec:
roots:
description: Deprecated. Use KeylessAttestor instead.
type: string
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped.
+ At least one pattern in the list must match the
+ image for the rule to be skipped. Each image reference
+ consists of a registry address (defaults to docker.io),
+ repository, image, and tag (defaults to latest).
+ Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
subject:
description: Deprecated. Use KeylessAttestor instead.
type: string
@@ -13100,6 +13122,17 @@ spec:
i.e. have matched passed a signature or attestation
check.
type: boolean
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped. At
+ least one pattern in the list must match the image for
+ the rule to be skipped. Each image reference consists
+ of a registry address (defaults to docker.io), repository,
+ image, and tag (defaults to latest). Wildcards (''*''
+ and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
type:
description: Type specifies the method of signature validation.
The allowed options are Cosign and Notary. By default
@@ -17655,6 +17688,17 @@ spec:
roots:
description: Deprecated. Use KeylessAttestor instead.
type: string
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped.
+ At least one pattern in the list must match the
+ image for the rule to be skipped. Each image reference
+ consists of a registry address (defaults to docker.io),
+ repository, image, and tag (defaults to latest).
+ Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
subject:
description: Deprecated. Use KeylessAttestor instead.
type: string
diff --git a/config/crds/kyverno.io_clusterpolicies.yaml b/config/crds/kyverno.io_clusterpolicies.yaml
index 5b7de6c526..e001acdffd 100644
--- a/config/crds/kyverno.io_clusterpolicies.yaml
+++ b/config/crds/kyverno.io_clusterpolicies.yaml
@@ -4340,6 +4340,17 @@ spec:
roots:
description: Deprecated. Use KeylessAttestor instead.
type: string
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped. At
+ least one pattern in the list must match the image for
+ the rule to be skipped. Each image reference consists
+ of a registry address (defaults to docker.io), repository,
+ image, and tag (defaults to latest). Wildcards (''*''
+ and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
subject:
description: Deprecated. Use KeylessAttestor instead.
type: string
@@ -8898,6 +8909,17 @@ spec:
roots:
description: Deprecated. Use KeylessAttestor instead.
type: string
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped.
+ At least one pattern in the list must match the
+ image for the rule to be skipped. Each image reference
+ consists of a registry address (defaults to docker.io),
+ repository, image, and tag (defaults to latest).
+ Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
subject:
description: Deprecated. Use KeylessAttestor instead.
type: string
@@ -13097,6 +13119,17 @@ spec:
i.e. have matched passed a signature or attestation
check.
type: boolean
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped. At
+ least one pattern in the list must match the image for
+ the rule to be skipped. Each image reference consists
+ of a registry address (defaults to docker.io), repository,
+ image, and tag (defaults to latest). Wildcards (''*''
+ and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
type:
description: Type specifies the method of signature validation.
The allowed options are Cosign and Notary. By default
@@ -17652,6 +17685,17 @@ spec:
roots:
description: Deprecated. Use KeylessAttestor instead.
type: string
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped.
+ At least one pattern in the list must match the
+ image for the rule to be skipped. Each image reference
+ consists of a registry address (defaults to docker.io),
+ repository, image, and tag (defaults to latest).
+ Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
subject:
description: Deprecated. Use KeylessAttestor instead.
type: string
diff --git a/config/crds/kyverno.io_policies.yaml b/config/crds/kyverno.io_policies.yaml
index 794b75d934..47f889a79e 100644
--- a/config/crds/kyverno.io_policies.yaml
+++ b/config/crds/kyverno.io_policies.yaml
@@ -4341,6 +4341,17 @@ spec:
roots:
description: Deprecated. Use KeylessAttestor instead.
type: string
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped. At
+ least one pattern in the list must match the image for
+ the rule to be skipped. Each image reference consists
+ of a registry address (defaults to docker.io), repository,
+ image, and tag (defaults to latest). Wildcards (''*''
+ and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
subject:
description: Deprecated. Use KeylessAttestor instead.
type: string
@@ -8900,6 +8911,17 @@ spec:
roots:
description: Deprecated. Use KeylessAttestor instead.
type: string
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped.
+ At least one pattern in the list must match the
+ image for the rule to be skipped. Each image reference
+ consists of a registry address (defaults to docker.io),
+ repository, image, and tag (defaults to latest).
+ Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
subject:
description: Deprecated. Use KeylessAttestor instead.
type: string
@@ -13100,6 +13122,17 @@ spec:
i.e. have matched passed a signature or attestation
check.
type: boolean
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped. At
+ least one pattern in the list must match the image for
+ the rule to be skipped. Each image reference consists
+ of a registry address (defaults to docker.io), repository,
+ image, and tag (defaults to latest). Wildcards (''*''
+ and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
type:
description: Type specifies the method of signature validation.
The allowed options are Cosign and Notary. By default
@@ -17655,6 +17688,17 @@ spec:
roots:
description: Deprecated. Use KeylessAttestor instead.
type: string
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped.
+ At least one pattern in the list must match the
+ image for the rule to be skipped. Each image reference
+ consists of a registry address (defaults to docker.io),
+ repository, image, and tag (defaults to latest).
+ Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
subject:
description: Deprecated. Use KeylessAttestor instead.
type: string
diff --git a/config/install-latest-testing.yaml b/config/install-latest-testing.yaml
index 609db3a0a3..2f462668ae 100644
--- a/config/install-latest-testing.yaml
+++ b/config/install-latest-testing.yaml
@@ -14510,6 +14510,17 @@ spec:
roots:
description: Deprecated. Use KeylessAttestor instead.
type: string
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped. At
+ least one pattern in the list must match the image for
+ the rule to be skipped. Each image reference consists
+ of a registry address (defaults to docker.io), repository,
+ image, and tag (defaults to latest). Wildcards (''*''
+ and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
subject:
description: Deprecated. Use KeylessAttestor instead.
type: string
@@ -19068,6 +19079,17 @@ spec:
roots:
description: Deprecated. Use KeylessAttestor instead.
type: string
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped.
+ At least one pattern in the list must match the
+ image for the rule to be skipped. Each image reference
+ consists of a registry address (defaults to docker.io),
+ repository, image, and tag (defaults to latest).
+ Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
subject:
description: Deprecated. Use KeylessAttestor instead.
type: string
@@ -23267,6 +23289,17 @@ spec:
i.e. have matched passed a signature or attestation
check.
type: boolean
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped. At
+ least one pattern in the list must match the image for
+ the rule to be skipped. Each image reference consists
+ of a registry address (defaults to docker.io), repository,
+ image, and tag (defaults to latest). Wildcards (''*''
+ and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
type:
description: Type specifies the method of signature validation.
The allowed options are Cosign and Notary. By default
@@ -27822,6 +27855,17 @@ spec:
roots:
description: Deprecated. Use KeylessAttestor instead.
type: string
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped.
+ At least one pattern in the list must match the
+ image for the rule to be skipped. Each image reference
+ consists of a registry address (defaults to docker.io),
+ repository, image, and tag (defaults to latest).
+ Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
subject:
description: Deprecated. Use KeylessAttestor instead.
type: string
@@ -32320,6 +32364,17 @@ spec:
roots:
description: Deprecated. Use KeylessAttestor instead.
type: string
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped. At
+ least one pattern in the list must match the image for
+ the rule to be skipped. Each image reference consists
+ of a registry address (defaults to docker.io), repository,
+ image, and tag (defaults to latest). Wildcards (''*''
+ and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
subject:
description: Deprecated. Use KeylessAttestor instead.
type: string
@@ -36879,6 +36934,17 @@ spec:
roots:
description: Deprecated. Use KeylessAttestor instead.
type: string
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped.
+ At least one pattern in the list must match the
+ image for the rule to be skipped. Each image reference
+ consists of a registry address (defaults to docker.io),
+ repository, image, and tag (defaults to latest).
+ Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
subject:
description: Deprecated. Use KeylessAttestor instead.
type: string
@@ -41079,6 +41145,17 @@ spec:
i.e. have matched passed a signature or attestation
check.
type: boolean
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped. At
+ least one pattern in the list must match the image for
+ the rule to be skipped. Each image reference consists
+ of a registry address (defaults to docker.io), repository,
+ image, and tag (defaults to latest). Wildcards (''*''
+ and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
type:
description: Type specifies the method of signature validation.
The allowed options are Cosign and Notary. By default
@@ -45634,6 +45711,17 @@ spec:
roots:
description: Deprecated. Use KeylessAttestor instead.
type: string
+ skipImageReferences:
+ description: 'SkipImageReferences is a list of matching
+ image reference patterns that should be skipped.
+ At least one pattern in the list must match the
+ image for the rule to be skipped. Each image reference
+ consists of a registry address (defaults to docker.io),
+ repository, image, and tag (defaults to latest).
+ Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.'
+ items:
+ type: string
+ type: array
subject:
description: Deprecated. Use KeylessAttestor instead.
type: string
diff --git a/docs/user/crd/index.html b/docs/user/crd/index.html
index 210d7f0249..c81187fe02 100644
--- a/docs/user/crd/index.html
+++ b/docs/user/crd/index.html
@@ -2187,6 +2187,20 @@ Wildcards (‘*’ and ‘?’) are allowed. See: https://kubernetes.io/docs/concepts/containers/images.
+
+
+
+
key
string
@@ -9033,6 +9047,20 @@ Wildcards (‘*’ and ‘?’) are allowed. See: https://kubernetes.io/docs/concepts/containers/images.
+ |
+
+
+
attestors
diff --git a/pkg/client/applyconfigurations/kyverno/v1/imageverification.go b/pkg/client/applyconfigurations/kyverno/v1/imageverification.go
index 04d051bd5c..60920acaf1 100644
--- a/pkg/client/applyconfigurations/kyverno/v1/imageverification.go
+++ b/pkg/client/applyconfigurations/kyverno/v1/imageverification.go
@@ -28,6 +28,7 @@ type ImageVerificationApplyConfiguration struct {
Type *v1.ImageVerificationType `json:"type,omitempty"`
Image *string `json:"image,omitempty"`
ImageReferences []string `json:"imageReferences,omitempty"`
+ SkipImageReferences []string `json:"skipImageReferences,omitempty"`
Key *string `json:"key,omitempty"`
Roots *string `json:"roots,omitempty"`
Subject *string `json:"subject,omitempty"`
@@ -76,6 +77,16 @@ func (b *ImageVerificationApplyConfiguration) WithImageReferences(values ...stri
return b
}
+// WithSkipImageReferences adds the given value to the SkipImageReferences field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, values provided by each call will be appended to the SkipImageReferences field.
+func (b *ImageVerificationApplyConfiguration) WithSkipImageReferences(values ...string) *ImageVerificationApplyConfiguration {
+ for i := range values {
+ b.SkipImageReferences = append(b.SkipImageReferences, values[i])
+ }
+ return b
+}
+
// WithKey sets the Key field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Key field is set to the value of the last call.
diff --git a/pkg/client/applyconfigurations/kyverno/v2beta1/imageverification.go b/pkg/client/applyconfigurations/kyverno/v2beta1/imageverification.go
index 4eab917fed..cf92439553 100644
--- a/pkg/client/applyconfigurations/kyverno/v2beta1/imageverification.go
+++ b/pkg/client/applyconfigurations/kyverno/v2beta1/imageverification.go
@@ -28,6 +28,7 @@ import (
type ImageVerificationApplyConfiguration struct {
Type *v1.ImageVerificationType `json:"type,omitempty"`
ImageReferences []string `json:"imageReferences,omitempty"`
+ SkipImageReferences []string `json:"skipImageReferences,omitempty"`
Attestors []kyvernov1.AttestorSetApplyConfiguration `json:"attestors,omitempty"`
Attestations []kyvernov1.AttestationApplyConfiguration `json:"attestations,omitempty"`
Repository *string `json:"repository,omitempty"`
@@ -62,6 +63,16 @@ func (b *ImageVerificationApplyConfiguration) WithImageReferences(values ...stri
return b
}
+// WithSkipImageReferences adds the given value to the SkipImageReferences field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, values provided by each call will be appended to the SkipImageReferences field.
+func (b *ImageVerificationApplyConfiguration) WithSkipImageReferences(values ...string) *ImageVerificationApplyConfiguration {
+ for i := range values {
+ b.SkipImageReferences = append(b.SkipImageReferences, values[i])
+ }
+ return b
+}
+
// WithAttestors adds the given value to the Attestors field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, values provided by each call will be appended to the Attestors field.
diff --git a/pkg/engine/api/imageverifymetadata.go b/pkg/engine/api/imageverifymetadata.go
index e8b5691d6c..8c3ea12894 100644
--- a/pkg/engine/api/imageverifymetadata.go
+++ b/pkg/engine/api/imageverifymetadata.go
@@ -10,13 +10,21 @@ import (
"gomodules.xyz/jsonpatch/v2"
)
+type ImageVerificationMetadataStatus string
+
+const (
+ ImageVerificationPass ImageVerificationMetadataStatus = "pass"
+ ImageVerificationFail ImageVerificationMetadataStatus = "fail"
+ ImageVerificationSkip ImageVerificationMetadataStatus = "skip"
+)
+
type ImageVerificationMetadata struct {
- Data map[string]bool `json:"data"`
+ Data map[string]ImageVerificationMetadataStatus `json:"data"`
}
-func (ivm *ImageVerificationMetadata) Add(image string, verified bool) {
+func (ivm *ImageVerificationMetadata) Add(image string, verified ImageVerificationMetadataStatus) {
if ivm.Data == nil {
- ivm.Data = make(map[string]bool)
+ ivm.Data = make(map[string]ImageVerificationMetadataStatus)
}
ivm.Data[image] = verified
}
@@ -29,11 +37,22 @@ func (ivm *ImageVerificationMetadata) IsVerified(image string) bool {
if !ok {
return false
}
+ return verified == ImageVerificationPass || verified == ImageVerificationSkip
+}
+
+func (ivm *ImageVerificationMetadata) ImageVerificationStatus(image string) ImageVerificationMetadataStatus {
+ if ivm.Data == nil {
+ return ImageVerificationFail
+ }
+ verified, ok := ivm.Data[image]
+ if !ok {
+ return ImageVerificationFail
+ }
return verified
}
func ParseImageMetadata(jsonData string) (*ImageVerificationMetadata, error) {
- var data map[string]bool
+ var data map[string]ImageVerificationMetadataStatus
if err := json.Unmarshal([]byte(jsonData), &data); err != nil {
return nil, err
}
diff --git a/pkg/engine/api/imageverifymetadata_test.go b/pkg/engine/api/imageverifymetadata_test.go
index bf4da4296e..e834194cd2 100644
--- a/pkg/engine/api/imageverifymetadata_test.go
+++ b/pkg/engine/api/imageverifymetadata_test.go
@@ -10,7 +10,7 @@ import (
func TestImageVerificationMetadata_IsVerified(t *testing.T) {
type fields struct {
- Data map[string]bool
+ Data map[string]ImageVerificationMetadataStatus
}
type args struct {
image string
@@ -22,8 +22,8 @@ func TestImageVerificationMetadata_IsVerified(t *testing.T) {
want bool
}{{
fields: fields{
- Data: map[string]bool{
- "test": true,
+ Data: map[string]ImageVerificationMetadataStatus{
+ "test": ImageVerificationPass,
},
},
args: args{
@@ -32,8 +32,8 @@ func TestImageVerificationMetadata_IsVerified(t *testing.T) {
want: true,
}, {
fields: fields{
- Data: map[string]bool{
- "test": true,
+ Data: map[string]ImageVerificationMetadataStatus{
+ "test": ImageVerificationPass,
},
},
args: args{
@@ -42,8 +42,8 @@ func TestImageVerificationMetadata_IsVerified(t *testing.T) {
want: false,
}, {
fields: fields{
- Data: map[string]bool{
- "test2": false,
+ Data: map[string]ImageVerificationMetadataStatus{
+ "test2": ImageVerificationFail,
},
},
args: args{
@@ -70,11 +70,11 @@ func TestImageVerificationMetadata_IsVerified(t *testing.T) {
func TestImageVerificationMetadata_Add(t *testing.T) {
type fields struct {
- Data map[string]bool
+ Data map[string]ImageVerificationMetadataStatus
}
type args struct {
image string
- verified bool
+ verified ImageVerificationMetadataStatus
}
tests := []struct {
name string
@@ -83,43 +83,43 @@ func TestImageVerificationMetadata_Add(t *testing.T) {
want *ImageVerificationMetadata
}{{
fields: fields{
- Data: map[string]bool{
- "test": true,
+ Data: map[string]ImageVerificationMetadataStatus{
+ "test": ImageVerificationPass,
},
},
args: args{
image: "test",
- verified: false,
+ verified: ImageVerificationFail,
},
want: &ImageVerificationMetadata{
- Data: map[string]bool{
- "test": false,
+ Data: map[string]ImageVerificationMetadataStatus{
+ "test": ImageVerificationFail,
},
},
}, {
args: args{
image: "test",
- verified: false,
+ verified: ImageVerificationFail,
},
want: &ImageVerificationMetadata{
- Data: map[string]bool{
- "test": false,
+ Data: map[string]ImageVerificationMetadataStatus{
+ "test": ImageVerificationFail,
},
},
}, {
fields: fields{
- Data: map[string]bool{
- "test": true,
+ Data: map[string]ImageVerificationMetadataStatus{
+ "test": ImageVerificationPass,
},
},
args: args{
image: "test2",
- verified: false,
+ verified: ImageVerificationFail,
},
want: &ImageVerificationMetadata{
- Data: map[string]bool{
- "test": true,
- "test2": false,
+ Data: map[string]ImageVerificationMetadataStatus{
+ "test": ImageVerificationPass,
+ "test2": ImageVerificationFail,
},
},
}}
@@ -152,21 +152,21 @@ func TestParseImageMetadata(t *testing.T) {
wantErr: true,
}, {
args: args{
- jsonData: `{"test":true}`,
+ jsonData: `{"test":"pass"}`,
},
want: &ImageVerificationMetadata{
- Data: map[string]bool{
- "test": true,
+ Data: map[string]ImageVerificationMetadataStatus{
+ "test": ImageVerificationPass,
},
},
}, {
args: args{
- jsonData: `{"test":true,"test2":false}`,
+ jsonData: `{"test":"pass","test2":"fail"}`,
},
want: &ImageVerificationMetadata{
- Data: map[string]bool{
- "test": true,
- "test2": false,
+ Data: map[string]ImageVerificationMetadataStatus{
+ "test": ImageVerificationPass,
+ "test2": ImageVerificationFail,
},
},
}}
@@ -186,7 +186,7 @@ func TestParseImageMetadata(t *testing.T) {
func TestImageVerificationMetadata_IsEmpty(t *testing.T) {
type fields struct {
- Data map[string]bool
+ Data map[string]ImageVerificationMetadataStatus
}
tests := []struct {
name string
@@ -194,8 +194,8 @@ func TestImageVerificationMetadata_IsEmpty(t *testing.T) {
want bool
}{{
fields: fields{
- Data: map[string]bool{
- "test": false,
+ Data: map[string]ImageVerificationMetadataStatus{
+ "test": ImageVerificationFail,
},
},
want: false,
@@ -216,7 +216,7 @@ func TestImageVerificationMetadata_IsEmpty(t *testing.T) {
func TestImageVerificationMetadata_Merge(t *testing.T) {
type fields struct {
- Data map[string]bool
+ Data map[string]ImageVerificationMetadataStatus
}
type args struct {
other ImageVerificationMetadata
@@ -230,39 +230,39 @@ func TestImageVerificationMetadata_Merge(t *testing.T) {
want: &ImageVerificationMetadata{},
}, {
fields: fields{
- Data: map[string]bool{
- "test": true,
+ Data: map[string]ImageVerificationMetadataStatus{
+ "test": ImageVerificationPass,
},
},
args: args{
other: ImageVerificationMetadata{
- Data: map[string]bool{
- "test": false,
+ Data: map[string]ImageVerificationMetadataStatus{
+ "test": ImageVerificationFail,
},
},
},
want: &ImageVerificationMetadata{
- Data: map[string]bool{
- "test": false,
+ Data: map[string]ImageVerificationMetadataStatus{
+ "test": ImageVerificationFail,
},
},
}, {
fields: fields{
- Data: map[string]bool{
- "test": true,
+ Data: map[string]ImageVerificationMetadataStatus{
+ "test": ImageVerificationPass,
},
},
args: args{
other: ImageVerificationMetadata{
- Data: map[string]bool{
- "test2": false,
+ Data: map[string]ImageVerificationMetadataStatus{
+ "test2": ImageVerificationFail,
},
},
},
want: &ImageVerificationMetadata{
- Data: map[string]bool{
- "test": true,
- "test2": false,
+ Data: map[string]ImageVerificationMetadataStatus{
+ "test": ImageVerificationPass,
+ "test2": ImageVerificationFail,
},
},
}}
@@ -297,7 +297,7 @@ func Test_makeAnnotationKeyForJSONPatch(t *testing.T) {
func TestImageVerificationMetadata_Patches(t *testing.T) {
type fields struct {
- Data map[string]bool
+ Data map[string]ImageVerificationMetadataStatus
}
type args struct {
hasAnnotations bool
@@ -311,8 +311,8 @@ func TestImageVerificationMetadata_Patches(t *testing.T) {
wantErr bool
}{{
fields: fields{
- Data: map[string]bool{
- "test": true,
+ Data: map[string]ImageVerificationMetadataStatus{
+ "test": ImageVerificationPass,
},
},
args: args{
@@ -321,12 +321,12 @@ func TestImageVerificationMetadata_Patches(t *testing.T) {
},
want: []string{
`{"op":"add","path":"/metadata/annotations","value":{}}`,
- `{"op":"add","path":"/metadata/annotations/kyverno.io~1verify-images","value":"{\"test\":true}"}`,
+ `{"op":"add","path":"/metadata/annotations/kyverno.io~1verify-images","value":"{\"test\":\"pass\"}"}`,
},
}, {
fields: fields{
- Data: map[string]bool{
- "test": true,
+ Data: map[string]ImageVerificationMetadataStatus{
+ "test": ImageVerificationPass,
},
},
args: args{
@@ -334,7 +334,7 @@ func TestImageVerificationMetadata_Patches(t *testing.T) {
log: logr.Discard(),
},
want: []string{
- `{"op":"add","path":"/metadata/annotations/kyverno.io~1verify-images","value":"{\"test\":true}"}`,
+ `{"op":"add","path":"/metadata/annotations/kyverno.io~1verify-images","value":"{\"test\":\"pass\"}"}`,
},
}, {
args: args{
diff --git a/pkg/engine/api/ruleresponse.go b/pkg/engine/api/ruleresponse.go
index 27222ee483..2c3ccd2ef0 100644
--- a/pkg/engine/api/ruleresponse.go
+++ b/pkg/engine/api/ruleresponse.go
@@ -44,14 +44,21 @@ type RuleResponse struct {
podSecurityChecks *PodSecurityChecks
// exception is the exception applied (if any)
exception *kyvernov2beta1.PolicyException
+ // emitWarning enable passing rule message as warning to api server warning header
+ emitWarning bool
}
func NewRuleResponse(name string, ruleType RuleType, msg string, status RuleStatus) *RuleResponse {
+ emitWarn := false
+ if status == RuleStatusError || status == RuleStatusFail || status == RuleStatusWarn {
+ emitWarn = true
+ }
return &RuleResponse{
- name: name,
- ruleType: ruleType,
- message: msg,
- status: status,
+ name: name,
+ ruleType: ruleType,
+ message: msg,
+ status: status,
+ emitWarning: emitWarn,
}
}
@@ -105,6 +112,11 @@ func (r RuleResponse) WithStats(stats ExecutionStats) RuleResponse {
return r
}
+func (r RuleResponse) WithEmitWarning(emitWarning bool) *RuleResponse {
+ r.emitWarning = emitWarning
+ return &r
+}
+
func (r *RuleResponse) Stats() ExecutionStats {
return r.stats
}
@@ -145,6 +157,10 @@ func (r *RuleResponse) Status() RuleStatus {
return r.status
}
+func (r *RuleResponse) EmitWarning() bool {
+ return r.emitWarning
+}
+
// HasStatus checks if rule status is in a given list
func (r *RuleResponse) HasStatus(status ...RuleStatus) bool {
for _, s := range status {
diff --git a/pkg/engine/handlers/validation/validate_image.go b/pkg/engine/handlers/validation/validate_image.go
index ed409b6784..66ba799cca 100644
--- a/pkg/engine/handlers/validation/validate_image.go
+++ b/pkg/engine/handlers/validation/validate_image.go
@@ -3,6 +3,7 @@ package validation
import (
"context"
"fmt"
+ "strings"
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
@@ -61,6 +62,8 @@ func (h validateImageHandler) Process(
}
}
+ skippedImages := make([]string, 0)
+ passedImages := make([]string, 0)
for _, v := range rule.VerifyImages {
imageVerify := v.Convert()
for _, infoMap := range policyContext.JSONContext().ImageInfo() {
@@ -73,31 +76,45 @@ func (h validateImageHandler) Process(
}
logger.V(4).Info("validating image", "image", image)
- if err := validateImage(policyContext, imageVerify, name, imageInfo, logger); err != nil {
+ if v, err := validateImage(policyContext, imageVerify, name, imageInfo, logger); err != nil {
return resource, handlers.WithFail(rule, engineapi.ImageVerify, err.Error())
+ } else if v == engineapi.ImageVerificationSkip {
+ skippedImages = append(skippedImages, image)
+ } else if v == engineapi.ImageVerificationPass {
+ passedImages = append(passedImages, image)
}
}
}
}
+
logger.V(4).Info("validated image", "rule", rule.Name)
- return resource, handlers.WithPass(rule, engineapi.Validation, "image verified")
+ if len(passedImages) > 0 || len(passedImages)+len(skippedImages) == 0 {
+ if len(skippedImages) > 0 {
+ return resource, handlers.WithPass(rule, engineapi.Validation, strings.Join(append([]string{"image verified, skipped images:"}, skippedImages...), " "))
+ }
+ return resource, handlers.WithPass(rule, engineapi.Validation, "image verified")
+ } else {
+ return resource, handlers.WithSkip(rule, engineapi.Validation, strings.Join(append([]string{"image skipped, skipped images:"}, skippedImages...), " "))
+ }
}
-func validateImage(ctx engineapi.PolicyContext, imageVerify *kyvernov1.ImageVerification, name string, imageInfo apiutils.ImageInfo, log logr.Logger) error {
+func validateImage(ctx engineapi.PolicyContext, imageVerify *kyvernov1.ImageVerification, name string, imageInfo apiutils.ImageInfo, log logr.Logger) (engineapi.ImageVerificationMetadataStatus, error) {
+ var verified engineapi.ImageVerificationMetadataStatus
+ var err error
image := imageInfo.String()
if imageVerify.VerifyDigest && imageInfo.Digest == "" {
log.V(2).Info("missing digest", "image", imageInfo.String())
- return fmt.Errorf("missing digest for %s", image)
+ return engineapi.ImageVerificationFail, fmt.Errorf("missing digest for %s", image)
}
newResource := ctx.NewResource()
if imageVerify.Required && newResource.Object != nil {
- verified, err := engineutils.IsImageVerified(newResource, image, log)
+ verified, err = engineutils.IsImageVerified(newResource, image, log)
if err != nil {
- return err
+ return engineapi.ImageVerificationFail, err
}
- if !verified {
- return fmt.Errorf("unverified image %s", image)
+ if verified == engineapi.ImageVerificationFail {
+ return engineapi.ImageVerificationFail, fmt.Errorf("unverified image %s", image)
}
}
- return nil
+ return verified, nil
}
diff --git a/pkg/engine/image_verify_test.go b/pkg/engine/image_verify_test.go
index 1d949b39a5..377bad2cab 100644
--- a/pkg/engine/image_verify_test.go
+++ b/pkg/engine/image_verify_test.go
@@ -972,7 +972,7 @@ func Test_MarkImageVerified(t *testing.T) {
verified, err := engineutils.IsImageVerified(resource, image, logr.Discard())
assert.NilError(t, err)
- assert.Equal(t, verified, true)
+ assert.Equal(t, verified, engineapi.ImageVerificationPass)
}
func testApplyPatches(t *testing.T, patches []jsonpatch.JsonPatchOperation) unstructured.Unstructured {
@@ -1360,3 +1360,126 @@ func Test_changePolicyCacheVerificationNotary(t *testing.T) {
errorAssertionUtil(t, image, ivm, er)
assert.Check(t, secondOperationTime > firstOperationTime/10 && secondOperationTime < firstOperationTime*10, "cache entry not found, so image verification should not be from cache.", firstOperationTime, secondOperationTime)
}
+
+var excludeVerifyImageNotaryPolicy = `{
+ "apiVersion": "kyverno.io/v2beta1",
+ "kind": "ClusterPolicy",
+ "metadata": {
+ "name": "check-image-notary"
+ },
+ "spec": {
+ "validationFailureAction": "Enforce",
+ "webhookTimeoutSeconds": 30,
+ "failurePolicy": "Fail",
+ "rules": [
+ {
+ "name": "verify-signature-notary",
+ "match": {
+ "any": [
+ {
+ "resources": {
+ "kinds": [
+ "Pod"
+ ]
+ }
+ }
+ ]
+ },
+ "verifyImages": [
+ {
+ "type": "Notary",
+ "imageReferences": [
+ "ghcr.io/*"
+ ],
+ "skipImageReferences" : [
+ "ghcr.io/invalid-user*"
+ ],
+ "attestors": [
+ {
+ "count": 1,
+ "entries": [
+ {
+ "certificates": {
+ "cert": "-----BEGIN CERTIFICATE-----\nMIIDTTCCAjWgAwIBAgIJAPI+zAzn4s0xMA0GCSqGSIb3DQEBCwUAMEwxCzAJBgNV\nBAYTAlVTMQswCQYDVQQIDAJXQTEQMA4GA1UEBwwHU2VhdHRsZTEPMA0GA1UECgwG\nTm90YXJ5MQ0wCwYDVQQDDAR0ZXN0MB4XDTIzMDUyMjIxMTUxOFoXDTMzMDUxOTIx\nMTUxOFowTDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdTZWF0\ndGxlMQ8wDQYDVQQKDAZOb3RhcnkxDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3\nDQEBAQUAA4IBDwAwggEKAoIBAQDNhTwv+QMk7jEHufFfIFlBjn2NiJaYPgL4eBS+\nb+o37ve5Zn9nzRppV6kGsa161r9s2KkLXmJrojNy6vo9a6g6RtZ3F6xKiWLUmbAL\nhVTCfYw/2n7xNlVMjyyUpE+7e193PF8HfQrfDFxe2JnX5LHtGe+X9vdvo2l41R6m\nIia04DvpMdG4+da2tKPzXIuLUz/FDb6IODO3+qsqQLwEKmmUee+KX+3yw8I6G1y0\nVp0mnHfsfutlHeG8gazCDlzEsuD4QJ9BKeRf2Vrb0ywqNLkGCbcCWF2H5Q80Iq/f\nETVO9z88R7WheVdEjUB8UrY7ZMLdADM14IPhY2Y+tLaSzEVZAgMBAAGjMjAwMAkG\nA1UdEwQCMAAwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA0G\nCSqGSIb3DQEBCwUAA4IBAQBX7x4Ucre8AIUmXZ5PUK/zUBVOrZZzR1YE8w86J4X9\nkYeTtlijf9i2LTZMfGuG0dEVFN4ae3CCpBst+ilhIndnoxTyzP+sNy4RCRQ2Y/k8\nZq235KIh7uucq96PL0qsF9s2RpTKXxyOGdtp9+HO0Ty5txJE2txtLDUIVPK5WNDF\nByCEQNhtHgN6V20b8KU2oLBZ9vyB8V010dQz0NRTDLhkcvJig00535/LUylECYAJ\n5/jn6XKt6UYCQJbVNzBg/YPGc1RF4xdsGVDBben/JXpeGEmkdmXPILTKd9tZ5TC0\nuOKpF5rWAruB5PCIrquamOejpXV9aQA/K2JQDuc0mcKz\n-----END CERTIFICATE-----"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}`
+
+var excludeVerifyImageNotaryResourcePass = `{
+ "apiVersion": "v1",
+ "kind": "Pod",
+ "metadata": {
+ "creationTimestamp": null,
+ "labels": {
+ "run": "test"
+ },
+ "name": "test",
+ "namespace": "default"
+ },
+ "spec": {
+ "containers": [
+ {
+ "image": "ghcr.io/kyverno/test-verify-image:signed",
+ "name": "test",
+ "resources": {}
+ }
+ ],
+ "dnsPolicy": "ClusterFirst",
+ "restartPolicy": "Always"
+ },
+ "status": {}
+}`
+
+var excludeVerifyImageNotaryResourceSkip = `{
+ "apiVersion": "v1",
+ "kind": "Pod",
+ "metadata": {
+ "creationTimestamp": null,
+ "labels": {
+ "run": "test"
+ },
+ "name": "testskip",
+ "namespace": "default"
+ },
+ "spec": {
+ "containers": [
+ {
+ "image": "ghcr.io/invalid-user/invalid-image:v1",
+ "name": "test",
+ "resources": {}
+ }
+ ],
+ "dnsPolicy": "ClusterFirst",
+ "restartPolicy": "Always"
+ },
+ "status": {}
+}`
+
+func Test_SkipImageReferences(t *testing.T) {
+ policyContextPass := buildContext(t, excludeVerifyImageNotaryPolicy, excludeVerifyImageNotaryResourcePass, "")
+
+ // Passes as image is included and not excluded
+ erPass, ivm := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContextPass, cfg)
+ assert.Equal(t, len(erPass.PolicyResponse.Rules), 1)
+ assert.Equal(t, erPass.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusPass,
+ fmt.Sprintf("expected: %v, got: %v, failure: %v",
+ engineapi.RuleStatusPass, erPass.PolicyResponse.Rules[0].Status(), erPass.PolicyResponse.Rules[0].Message()))
+ assert.Equal(t, ivm.IsEmpty(), false)
+
+ policyContextSkip := buildContext(t, excludeVerifyImageNotaryPolicy, excludeVerifyImageNotaryResourceSkip, "")
+
+ // Skipped as image is excluded
+ erSkip, _ := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContextSkip, cfg)
+ assert.Equal(t, len(erSkip.PolicyResponse.Rules), 1)
+ assert.Equal(t, erSkip.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusSkip,
+ fmt.Sprintf("expected: %v, got: %v, failure: %v",
+ engineapi.RuleStatusPass, erSkip.PolicyResponse.Rules[0].Status(), erSkip.PolicyResponse.Rules[0].Message()))
+}
diff --git a/pkg/engine/internal/imageverifier.go b/pkg/engine/internal/imageverifier.go
index 92fe60490d..7c04a75302 100644
--- a/pkg/engine/internal/imageverifier.go
+++ b/pkg/engine/internal/imageverifier.go
@@ -69,7 +69,7 @@ func HasImageVerifiedAnnotationChanged(ctx engineapi.PolicyContext, log logr.Log
if newValue == oldValue {
return false
}
- var newValueObj, oldValueObj map[string]bool
+ var newValueObj, oldValueObj map[string]engineapi.ImageVerificationMetadataStatus
err := json.Unmarshal([]byte(newValue), &newValueObj)
if err != nil {
log.Error(err, "failed to parse new resource annotation.")
@@ -93,7 +93,7 @@ func HasImageVerifiedAnnotationChanged(ctx engineapi.PolicyContext, log logr.Log
return false
}
-func matchImageReferences(imageReferences []string, image string) bool {
+func matchReferences(imageReferences []string, image string) bool {
for _, imageRef := range imageReferences {
if wildcard.Match(imageRef, image) {
return true
@@ -102,6 +102,23 @@ func matchImageReferences(imageReferences []string, image string) bool {
return false
}
+func ruleStatusToImageVerificationStatus(ruleStatus engineapi.RuleStatus) engineapi.ImageVerificationMetadataStatus {
+ var imageVerificationResult engineapi.ImageVerificationMetadataStatus
+ switch ruleStatus {
+ case engineapi.RuleStatusPass:
+ imageVerificationResult = engineapi.ImageVerificationPass
+ case engineapi.RuleStatusSkip:
+ imageVerificationResult = engineapi.ImageVerificationSkip
+ case engineapi.RuleStatusWarn:
+ imageVerificationResult = engineapi.ImageVerificationSkip
+ case engineapi.RuleStatusFail:
+ imageVerificationResult = engineapi.ImageVerificationFail
+ default:
+ imageVerificationResult = engineapi.ImageVerificationFail
+ }
+ return imageVerificationResult
+}
+
func isImageVerified(resource unstructured.Unstructured, image string, log logr.Logger) (bool, error) {
if resource.Object == nil {
return false, fmt.Errorf("nil resource")
@@ -234,14 +251,14 @@ func (iv *ImageVerifier) Verify(
changed, err := iv.policyContext.JSONContext().HasChanged(pointer)
if err == nil && !changed {
iv.logger.V(4).Info("no change in image, skipping check", "image", image)
- iv.ivm.Add(image, true)
+ iv.ivm.Add(image, engineapi.ImageVerificationPass)
continue
}
verified, err := isImageVerified(iv.policyContext.NewResource(), image, iv.logger)
if err == nil && verified {
iv.logger.Info("image was previously verified, skipping check", "image", image)
- iv.ivm.Add(image, true)
+ iv.ivm.Add(image, engineapi.ImageVerificationPass)
continue
}
start := time.Now()
@@ -295,7 +312,7 @@ func (iv *ImageVerifier) Verify(
if ruleResp != nil {
if len(imageVerify.Attestors) > 0 || len(imageVerify.Attestations) > 0 {
- iv.ivm.Add(image, ruleResp.Status() == engineapi.RuleStatusPass)
+ iv.ivm.Add(image, ruleStatusToImageVerificationStatus(ruleResp.Status()))
}
responses = append(responses, ruleResp)
}
@@ -324,8 +341,14 @@ func (iv *ImageVerifier) verifyImage(
return engineapi.RuleError(iv.rule.Name, engineapi.ImageVerify, fmt.Sprintf("failed to add image to context %s", image), err), ""
}
if len(imageVerify.Attestors) > 0 {
- if !matchImageReferences(imageVerify.ImageReferences, image) {
- return nil, ""
+ if !matchReferences(imageVerify.ImageReferences, image) {
+ return engineapi.RuleSkip(iv.rule.Name, engineapi.ImageVerify, fmt.Sprintf("skipping image reference image %s, policy %s ruleName %s", image, iv.policyContext.Policy().GetName(), iv.rule.Name)), ""
+ }
+
+ if matchReferences(imageVerify.SkipImageReferences, image) {
+ iv.logger.Info("skipping image reference", "image", image, "policy", iv.policyContext.Policy().GetName(), "ruleName", iv.rule.Name)
+ iv.ivm.Add(image, engineapi.ImageVerificationSkip)
+ return engineapi.RuleSkip(iv.rule.Name, engineapi.ImageVerify, fmt.Sprintf("skipping image reference image %s, policy %s ruleName %s", image, iv.policyContext.Policy().GetName(), iv.rule.Name)).WithEmitWarning(true), ""
}
ruleResp, cosignResp := iv.verifyAttestors(ctx, imageVerify.Attestors, imageVerify, imageInfo, "")
if ruleResp.Status() != engineapi.RuleStatusPass {
diff --git a/pkg/engine/utils/image.go b/pkg/engine/utils/image.go
index 00be6afb05..ff3300c352 100644
--- a/pkg/engine/utils/image.go
+++ b/pkg/engine/utils/image.go
@@ -21,7 +21,6 @@ func ImageMatches(image string, imagePatterns []string) bool {
return true
}
}
-
return false
}
@@ -66,19 +65,19 @@ func ExtractMatchingImages(
return matchingImages, imageRefs, nil
}
-func IsImageVerified(resource unstructured.Unstructured, image string, log logr.Logger) (bool, error) {
+func IsImageVerified(resource unstructured.Unstructured, image string, log logr.Logger) (engineapi.ImageVerificationMetadataStatus, error) {
if resource.Object == nil {
- return false, fmt.Errorf("nil resource")
+ return engineapi.ImageVerificationFail, fmt.Errorf("nil resource")
}
if annotations := resource.GetAnnotations(); len(annotations) == 0 {
- return false, nil
+ return engineapi.ImageVerificationFail, nil
} else if data, ok := annotations[kyverno.AnnotationImageVerify]; !ok {
log.V(2).Info("missing image metadata in annotation", "key", kyverno.AnnotationImageVerify)
- return false, fmt.Errorf("image is not verified")
+ return engineapi.ImageVerificationFail, fmt.Errorf("image is not verified")
} else if ivm, err := engineapi.ParseImageMetadata(data); err != nil {
log.Error(err, "failed to parse image verification metadata", "data", data)
- return false, fmt.Errorf("failed to parse image metadata: %w", err)
+ return engineapi.ImageVerificationFail, fmt.Errorf("failed to parse image metadata: %w", err)
} else {
- return ivm.IsVerified(image), nil
+ return ivm.ImageVerificationStatus(image), nil
}
}
diff --git a/pkg/webhooks/utils/warning.go b/pkg/webhooks/utils/warning.go
index 6119352097..cf810b4949 100644
--- a/pkg/webhooks/utils/warning.go
+++ b/pkg/webhooks/utils/warning.go
@@ -10,7 +10,7 @@ func GetWarningMessages(engineResponses []engineapi.EngineResponse) []string {
var warnings []string
for _, er := range engineResponses {
for _, rule := range er.PolicyResponse.Rules {
- if rule.Status() != engineapi.RuleStatusPass && rule.Status() != engineapi.RuleStatusSkip {
+ if rule.EmitWarning() {
msg := fmt.Sprintf("policy %s.%s: %s", er.Policy().GetName(), rule.Name(), rule.Message())
warnings = append(warnings, msg)
}
diff --git a/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/keyless-attestations-multiple-subjects-1/chainsaw-step-02-assert-1.yaml b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/keyless-attestations-multiple-subjects-1/chainsaw-step-02-assert-1.yaml
index 669073222c..ffa6c8bf7c 100755
--- a/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/keyless-attestations-multiple-subjects-1/chainsaw-step-02-assert-1.yaml
+++ b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/keyless-attestations-multiple-subjects-1/chainsaw-step-02-assert-1.yaml
@@ -2,7 +2,7 @@ apiVersion: v1
kind: Pod
metadata:
annotations:
- kyverno.io/verify-images: '{"ghcr.io/chipzoller/zulu@sha256:476b21f1a75dc90fac3579ee757f4607bb5546f476195cf645c54badf558c0db":true}'
+ kyverno.io/verify-images: '{"ghcr.io/chipzoller/zulu@sha256:476b21f1a75dc90fac3579ee757f4607bb5546f476195cf645c54badf558c0db":"pass"}'
name: zulu
namespace: default
spec:
diff --git a/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/keyless-attestations-multiple-subjects-2/chainsaw-step-02-assert-1.yaml b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/keyless-attestations-multiple-subjects-2/chainsaw-step-02-assert-1.yaml
index 669073222c..ffa6c8bf7c 100755
--- a/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/keyless-attestations-multiple-subjects-2/chainsaw-step-02-assert-1.yaml
+++ b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/keyless-attestations-multiple-subjects-2/chainsaw-step-02-assert-1.yaml
@@ -2,7 +2,7 @@ apiVersion: v1
kind: Pod
metadata:
annotations:
- kyverno.io/verify-images: '{"ghcr.io/chipzoller/zulu@sha256:476b21f1a75dc90fac3579ee757f4607bb5546f476195cf645c54badf558c0db":true}'
+ kyverno.io/verify-images: '{"ghcr.io/chipzoller/zulu@sha256:476b21f1a75dc90fac3579ee757f4607bb5546f476195cf645c54badf558c0db":"pass"}'
name: zulu
namespace: default
spec:
diff --git a/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/keyless-attestations-multiple-subjects-4/chainsaw-step-02-assert-1.yaml b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/keyless-attestations-multiple-subjects-4/chainsaw-step-02-assert-1.yaml
index 669073222c..ffa6c8bf7c 100755
--- a/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/keyless-attestations-multiple-subjects-4/chainsaw-step-02-assert-1.yaml
+++ b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/keyless-attestations-multiple-subjects-4/chainsaw-step-02-assert-1.yaml
@@ -2,7 +2,7 @@ apiVersion: v1
kind: Pod
metadata:
annotations:
- kyverno.io/verify-images: '{"ghcr.io/chipzoller/zulu@sha256:476b21f1a75dc90fac3579ee757f4607bb5546f476195cf645c54badf558c0db":true}'
+ kyverno.io/verify-images: '{"ghcr.io/chipzoller/zulu@sha256:476b21f1a75dc90fac3579ee757f4607bb5546f476195cf645c54badf558c0db":"pass"}'
name: zulu
namespace: default
spec:
diff --git a/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/keyless-attestations-multiple-subjects-counts-1/chainsaw-step-02-assert-1.yaml b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/keyless-attestations-multiple-subjects-counts-1/chainsaw-step-02-assert-1.yaml
index 669073222c..ffa6c8bf7c 100755
--- a/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/keyless-attestations-multiple-subjects-counts-1/chainsaw-step-02-assert-1.yaml
+++ b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/keyless-attestations-multiple-subjects-counts-1/chainsaw-step-02-assert-1.yaml
@@ -2,7 +2,7 @@ apiVersion: v1
kind: Pod
metadata:
annotations:
- kyverno.io/verify-images: '{"ghcr.io/chipzoller/zulu@sha256:476b21f1a75dc90fac3579ee757f4607bb5546f476195cf645c54badf558c0db":true}'
+ kyverno.io/verify-images: '{"ghcr.io/chipzoller/zulu@sha256:476b21f1a75dc90fac3579ee757f4607bb5546f476195cf645c54badf558c0db":"pass"}'
name: zulu
namespace: default
spec:
diff --git a/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/keyless-mutatedigest-verifydigest-required/chainsaw-step-02-assert-1.yaml b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/keyless-mutatedigest-verifydigest-required/chainsaw-step-02-assert-1.yaml
index 669073222c..ffa6c8bf7c 100755
--- a/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/keyless-mutatedigest-verifydigest-required/chainsaw-step-02-assert-1.yaml
+++ b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/keyless-mutatedigest-verifydigest-required/chainsaw-step-02-assert-1.yaml
@@ -2,7 +2,7 @@ apiVersion: v1
kind: Pod
metadata:
annotations:
- kyverno.io/verify-images: '{"ghcr.io/chipzoller/zulu@sha256:476b21f1a75dc90fac3579ee757f4607bb5546f476195cf645c54badf558c0db":true}'
+ kyverno.io/verify-images: '{"ghcr.io/chipzoller/zulu@sha256:476b21f1a75dc90fac3579ee757f4607bb5546f476195cf645c54badf558c0db":"pass"}'
name: zulu
namespace: default
spec:
diff --git a/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/keyless-nomutatedigest-noverifydigest-norequired/chainsaw-step-02-assert-1.yaml b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/keyless-nomutatedigest-noverifydigest-norequired/chainsaw-step-02-assert-1.yaml
index 7d1c2da8fb..a8984c9062 100755
--- a/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/keyless-nomutatedigest-noverifydigest-norequired/chainsaw-step-02-assert-1.yaml
+++ b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/keyless-nomutatedigest-noverifydigest-norequired/chainsaw-step-02-assert-1.yaml
@@ -2,7 +2,7 @@ apiVersion: v1
kind: Pod
metadata:
annotations:
- kyverno.io/verify-images: '{"ghcr.io/chipzoller/zulu:latest":true}'
+ kyverno.io/verify-images: '{"ghcr.io/chipzoller/zulu:latest":"pass"}'
name: zulu
namespace: default
spec:
diff --git a/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/keyless-nomutatedigest-noverifydigest-required/chainsaw-step-02-assert-1.yaml b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/keyless-nomutatedigest-noverifydigest-required/chainsaw-step-02-assert-1.yaml
index 7d1c2da8fb..a8984c9062 100755
--- a/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/keyless-nomutatedigest-noverifydigest-required/chainsaw-step-02-assert-1.yaml
+++ b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/keyless-nomutatedigest-noverifydigest-required/chainsaw-step-02-assert-1.yaml
@@ -2,7 +2,7 @@ apiVersion: v1
kind: Pod
metadata:
annotations:
- kyverno.io/verify-images: '{"ghcr.io/chipzoller/zulu:latest":true}'
+ kyverno.io/verify-images: '{"ghcr.io/chipzoller/zulu:latest":"pass"}'
name: zulu
namespace: default
spec:
diff --git a/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/README.md b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/README.md
new file mode 100644
index 0000000000..80e41e29ba
--- /dev/null
+++ b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/README.md
@@ -0,0 +1,12 @@
+## Description
+
+This test tests the exclude image references attribute.
+
+## Expected Behavior
+
+This test creates a cluster policy with exclude image references attribute.
+The pod that is not excluded will be verified and the pod that is excluded will be skipped
+
+## Reference Issue(s)
+
+8592
diff --git a/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/bad.yaml b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/bad.yaml
new file mode 100644
index 0000000000..eb3bed7c9f
--- /dev/null
+++ b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/bad.yaml
@@ -0,0 +1,19 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ run: test-bad
+ name: test-bad
+ namespace: exclude-refs
+spec:
+ containers:
+ - image: ghcr.io/chipzoller/zulu:v0.0.14@sha256:476b21f1a75dc90fac3579ee757f4607bb5546f476195cf645c54badf558c0db
+ name: test
+ resources: {}
+ - image: ghcr.io/kyverno/kyverno:latest
+ name: test
+ resources: {}
+ dnsPolicy: ClusterFirst
+ restartPolicy: Always
+status: {}
\ No newline at end of file
diff --git a/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/chainsaw-test.yaml b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/chainsaw-test.yaml
new file mode 100644
index 0000000000..5152679171
--- /dev/null
+++ b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/chainsaw-test.yaml
@@ -0,0 +1,34 @@
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+ creationTimestamp: null
+ name: skip-image-reference
+spec:
+ timeouts:
+ delete: 2m
+ steps:
+ - name: step-01
+ try:
+ - apply:
+ file: policy.yaml
+ - assert:
+ file: policy-ready.yaml
+ - name: step-02
+ try:
+ - apply:
+ file: pod.yaml
+ - assert:
+ file: pod-assert.yaml
+ - name: step-03
+ try:
+ - apply:
+ file: skipped.yaml
+ - assert:
+ file: skipped-assert.yaml
+ - name: step-04
+ try:
+ - apply:
+ expect:
+ - check:
+ ($error != null): true
+ file: bad.yaml
\ No newline at end of file
diff --git a/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/pod-assert.yaml b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/pod-assert.yaml
new file mode 100644
index 0000000000..82022e66bb
--- /dev/null
+++ b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/pod-assert.yaml
@@ -0,0 +1,5 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ name: test
+ namespace: exclude-refs
\ No newline at end of file
diff --git a/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/pod.yaml b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/pod.yaml
new file mode 100644
index 0000000000..27fe6ff841
--- /dev/null
+++ b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/pod.yaml
@@ -0,0 +1,16 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ run: test
+ name: test
+ namespace: exclude-refs
+spec:
+ containers:
+ - image: ghcr.io/kyverno/test-verify-image:signed
+ name: test
+ resources: {}
+ dnsPolicy: ClusterFirst
+ restartPolicy: Always
+status: {}
\ No newline at end of file
diff --git a/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/policy-ready.yaml b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/policy-ready.yaml
new file mode 100644
index 0000000000..56444a2e18
--- /dev/null
+++ b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/policy-ready.yaml
@@ -0,0 +1,9 @@
+apiVersion: kyverno.io/v2beta1
+kind: ClusterPolicy
+metadata:
+ name: verify-exclude-refs
+status:
+ conditions:
+ - reason: Succeeded
+ status: "True"
+ type: Ready
\ No newline at end of file
diff --git a/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/policy.yaml b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/policy.yaml
new file mode 100755
index 0000000000..339878346c
--- /dev/null
+++ b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/policy.yaml
@@ -0,0 +1,66 @@
+apiVersion: v1
+kind: Namespace
+metadata:
+ name: exclude-refs
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: keys
+ namespace: exclude-refs
+data:
+ certificate: |-
+ -----BEGIN CERTIFICATE-----
+ MIIDTTCCAjWgAwIBAgIJAPI+zAzn4s0xMA0GCSqGSIb3DQEBCwUAMEwxCzAJBgNV
+ BAYTAlVTMQswCQYDVQQIDAJXQTEQMA4GA1UEBwwHU2VhdHRsZTEPMA0GA1UECgwG
+ Tm90YXJ5MQ0wCwYDVQQDDAR0ZXN0MB4XDTIzMDUyMjIxMTUxOFoXDTMzMDUxOTIx
+ MTUxOFowTDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdTZWF0
+ dGxlMQ8wDQYDVQQKDAZOb3RhcnkxDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3
+ DQEBAQUAA4IBDwAwggEKAoIBAQDNhTwv+QMk7jEHufFfIFlBjn2NiJaYPgL4eBS+
+ b+o37ve5Zn9nzRppV6kGsa161r9s2KkLXmJrojNy6vo9a6g6RtZ3F6xKiWLUmbAL
+ hVTCfYw/2n7xNlVMjyyUpE+7e193PF8HfQrfDFxe2JnX5LHtGe+X9vdvo2l41R6m
+ Iia04DvpMdG4+da2tKPzXIuLUz/FDb6IODO3+qsqQLwEKmmUee+KX+3yw8I6G1y0
+ Vp0mnHfsfutlHeG8gazCDlzEsuD4QJ9BKeRf2Vrb0ywqNLkGCbcCWF2H5Q80Iq/f
+ ETVO9z88R7WheVdEjUB8UrY7ZMLdADM14IPhY2Y+tLaSzEVZAgMBAAGjMjAwMAkG
+ A1UdEwQCMAAwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA0G
+ CSqGSIb3DQEBCwUAA4IBAQBX7x4Ucre8AIUmXZ5PUK/zUBVOrZZzR1YE8w86J4X9
+ kYeTtlijf9i2LTZMfGuG0dEVFN4ae3CCpBst+ilhIndnoxTyzP+sNy4RCRQ2Y/k8
+ Zq235KIh7uucq96PL0qsF9s2RpTKXxyOGdtp9+HO0Ty5txJE2txtLDUIVPK5WNDF
+ ByCEQNhtHgN6V20b8KU2oLBZ9vyB8V010dQz0NRTDLhkcvJig00535/LUylECYAJ
+ 5/jn6XKt6UYCQJbVNzBg/YPGc1RF4xdsGVDBben/JXpeGEmkdmXPILTKd9tZ5TC0
+ uOKpF5rWAruB5PCIrquamOejpXV9aQA/K2JQDuc0mcKz
+ -----END CERTIFICATE-----
+---
+apiVersion: kyverno.io/v2beta1
+kind: ClusterPolicy
+metadata:
+ name: verify-exclude-refs
+spec:
+ validationFailureAction: Enforce
+ webhookTimeoutSeconds: 30
+ failurePolicy: Fail
+ rules:
+ - name: verify-exclude-refs
+ context:
+ - name: keys
+ configMap:
+ name: keys
+ namespace: exclude-refs
+ match:
+ any:
+ - resources:
+ kinds:
+ - Pod
+ verifyImages:
+ - type: Notary
+ imageReferences:
+ - "ghcr.io/*"
+ skipImageReferences:
+ - "ghcr.io/chipzoller*"
+ attestors:
+ - count: 1
+ entries:
+ - certificates:
+ cert: "{{ keys.data.certificate }}"
+ name: keys
+ namespace: test-verify-images
diff --git a/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/skipped-assert.yaml b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/skipped-assert.yaml
new file mode 100644
index 0000000000..6fc45dcc6a
--- /dev/null
+++ b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/skipped-assert.yaml
@@ -0,0 +1,5 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ name: test-skipped
+ namespace: exclude-refs
\ No newline at end of file
diff --git a/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/skipped.yaml b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/skipped.yaml
new file mode 100644
index 0000000000..7264419a7f
--- /dev/null
+++ b/test/conformance/chainsaw/verifyImages/clusterpolicy/standard/skip-image-reference/skipped.yaml
@@ -0,0 +1,16 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ creationTimestamp: null
+ labels:
+ run: test-skipped
+ name: test-skipped
+ namespace: exclude-refs
+spec:
+ containers:
+ - image: ghcr.io/chipzoller/zulu:v0.0.14@sha256:476b21f1a75dc90fac3579ee757f4607bb5546f476195cf645c54badf558c0db
+ name: test
+ resources: {}
+ dnsPolicy: ClusterFirst
+ restartPolicy: Always
+status: {}
\ No newline at end of file
|