Skip to content

ImageValidatingPolicy

FEATURE STATE: Stable Kyverno v1.19

The Kyverno ImageValidatingPolicy type is a Kyverno policy type designed for verifying container image signatures and attestations.

The ImageValidatingPolicy extends the Kyverno ValidatingPolicy with the following additional fields for image verification features. A complete reference is provided in the API specification.

When Kubernetes resources are evaluated images for pods and pod templates are automatically extracted for processing. For custom resources, or for JSON payloads, the images field can be used to declare CEL expressions that extract images from the payload.

For example, this policy declaration will process the image specified in the imageReference field:

apiVersion: policies.kyverno.io/v1
kind: ImageValidatingPolicy
metadata:
name: sample
spec:
evaluation:
mode: JSON
images:
- name: imagerefs
expression: '[object.imageReference]'
# ...

The spec.matchImageReferences field defines rules for matching container images. It allows specifying glob patterns or CEL expressions that specify which images the policy should match.

apiVersion: policies.kyverno.io/v1
kind: ImageValidatingPolicy
metadata:
name: check-images
spec:
matchImageReferences: # At least one sub-field is required
- glob: 'ghcr.io/kyverno/*' # Match images using glob pattern
- expression: "image.registry == 'ghcr.io'" # Match using CEL expression
# ...

The attestors field declares trusted signing authorities, such as keys or certificates.

Cosign attestors: These use public keys, keyless signing, transparency logs, certificates, or TUF-based metadata for image validation.

apiVersion: policies.kyverno.io/v1
kind: ImageValidatingPolicy
metadata:
name: check-images
spec:
matchConstraints:
resourceRules:
- apiGroups: ['']
apiVersions: ['v1']
operations: ['CREATE']
resources: ['pods']
variables:
- name: cm
expression: >-
resource.Get("v1", "configmaps", object.metadata.namespace, "keys")
attestors:
- name: cosign # A unique name to identify this attestor
cosign:
key: # Public key-based verification and At least one sub-field is required
expression: variables.cm.data.pubKey # CEL expression that resolves to the public key
kms: 'gcpkms://...' # KMS URI for key verification (e.g., GCP KMS, AWS KMS)
hashAlgorithm: 'sha256' # Optional hash algorithm used with the key
data:
| # Direct inline public key data (optional if secretRef or kms is used)
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
keyless: # Keyless signing verification (OIDC-based)
identities: # List of accepted signing identities
- subject: 'https://github.com/myorg/myrepo/.github/workflows/deploy.yaml@refs/heads/main'
issuer: 'https://token.actions.githubusercontent.com'
subjectRegExp: ".*github\\.com/.*/.*/.github/workflows/.*" # Optional regex for subject matching
issuerRegExp: "https://token\\.actions\\.githubusercontent\\.com" # Optional regex for issuer matching
trustedRoot:
expression: variables.githubTrustedRoot # CEL expression resolving to trusted root JSON
roots:
| # Roots is an optional set of PEM encoded trusted root certificates. If not provided, the system roots are used.
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
ctlog: # Transparency log settings (e.g., Rekor)
url: 'https://rekor.sigstore.dev'
rekorPubKey: | # Public key for verifying Rekor entries
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
ctLogPubKey: | # Public key for verifying CT log entries (optional)
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
tsaCertChain:
| # Certificate chain for Time Stamp Authority (optional)
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
insecureIgnoreTlog: false # Skip TLog verification (for testing only)
insecureIgnoreSCT: false # Skip Signed Certificate Timestamp (for testing only)
certificate: # Certificate-based verification and At least one sub-field is required
cert:
value: | # Inline signing certificate
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
expression: variables.cm.data.cert # CEL expression resolving to certificate
certChain: # At least one sub-field is required
value: | # Certificate chain associated with the signer o
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
expression: variables.cm.data.certChain # CEL expression resolving to certificate
source: # Optional metadata to constrain image source (optional)
repository: 'ghcr.io/myorg/myimage' # Limit to specific image repo
pullSecrets: # Kubernetes secrets used to access the registry
- name: my-registry-secret
tagPrefix: 'v1.' # Restrict verification to images starting with this tag
tuf:
root:
path: '/var/run/tuf/root.json' # Local path to TUF root metadata (optional)
data: | # Optional base64-encoded TUF root metadata (optional)
eyJzaWduZWQiOiB7Li4ufSwgInNpZ25hdHVyZXMiOiBbLi4uXX0=
mirror: 'https://tuf.example.org' # Sigstore TUF mirror URL (optional)
# ...

Notary attestors: These use certificates and optional Time Stamp Authority (TSA) certificates for image signature verification.

apiVersion: policies.kyverno.io/v1
kind: ImageValidatingPolicy
metadata:
name: check-images
spec:
matchConstraints:
resourceRules:
- apiGroups:
- ''
apiVersions:
- v1
operations:
- CREATE
resources:
- pods
variables:
- name: cm
expression: 'resource.Get("v1", "configmaps", object.metadata.namespace, "keys")'
matchImageReferences:
- glob: ghcr.io/*
attestors:
- name: notary
notary:
certs:
value: |
-----BEGIN CERTIFICATE-----
MIIBjTCCATOgAwIBAgIUdMiN3gC...
-----END CERTIFICATE-----
expression: variables.cm.data.cert
tsaCerts:
value: |
-----BEGIN CERTIFICATE-----
MIIC4jCCAcqgAwIBAgIQAm3T2tWk...
-----END CERTIFICATE-----
expression: variables.cm.data.tsaCert

The attestations field specifies additional metadata to validate.

apiVersion: policies.kyverno.io/v1
kind: ImageValidatingPolicy
metadata:
name: check-images
spec:
matchConstraints:
resourceRules:
- apiGroups: ['']
apiVersions: ['v1']
operations: ['CREATE']
resources: ['pods']
matchImageReferences:
- glob: ghcr.io/*
attestors:
- name: notary
notary:
certs:
value: |-
-----BEGIN CERTIFICATE-----
MIIBjTCCATOgAwIBAgIUdMiN3gC...
-----END CERTIFICATE-----
attestations:
- name: sbom # Logical name for this attestation
referrer: # Uses OCI artifact type for verification
type: sbom/cyclone-dx
- name: toto # Another attestation named `toto`
intoto:
type: https://example.com/attestations/slsa-provenance/v0.2 # Predicate type URI for in-toto format

The validationConfigurations field defines settings for mutating image tags to digests, verifying that images are using digests, and enforcing image validation requirements across policies.

apiVersion: policies.kyverno.io/v1
kind: ImageValidatingPolicy
metadata:
name: check-images
spec:
matchImageReferences:
- glob: ghcr.io/*
validationConfigurations:
mutateDigest: true # Mutates image tags to digests (recommended to avoid mutable tags).
required: true # Enforces that images must be validated according to policies.
verifyDigest: true # Ensures that images are verified with a digest instead of tags.

Credentials specify the authentication information required to securely access and interact with a private registry. The credentials field supports two mechanisms: ambient cloud credential providers and Kubernetes secrets containing registry login data.

The following credential features are not yet supported by ImageValidatingPolicy:

  • No automatic pod imagePullSecrets: IVPOL does not automatically pick up imagePullSecrets declared on the Pod/Deployment being admitted. You must explicitly list secrets in spec.credentials.secrets.
  • No cross-namespace secrets: secrets must exist in the Kyverno namespace (typically kyverno). The namespace/name notation is not supported; you must copy or sync the secret into the Kyverno namespace.
  • No global credential helpers: providers must be declared per-policy (see above).

The providers field enables ambient cloud credential helpers. Each value activates the corresponding credential helper for registry authentication:

ProviderRegistryTypical auth mechanism
defaultAny registry supporting Docker credential helpersDocker config / keychain
amazonAmazon ECR (*.dkr.ecr.*.amazonaws.com)IRSA / IAM instance profile
googleGoogle Artifact Registry / GCRWorkload Identity / ADC
azureAzure Container RegistryWorkload Identity / managed identity
githubGitHub Container Registry (ghcr.io)GitHub token via credential helper

When using cloud IAM (e.g. IRSA on AWS, Workload Identity on GKE), you only need to add the matching provider, no secret is required, provided the Kyverno admission controller pod has the appropriate IAM permissions.

The secrets field accepts a list of Kubernetes Secret names (of type kubernetes.io/dockerconfigjson) used to authenticate against private registries.

Example: public registry (no credentials needed)

Section titled “Example: public registry (no credentials needed)”
apiVersion: policies.kyverno.io/v1
kind: ImageValidatingPolicy
metadata:
name: check-images
spec:
matchImageReferences:
- glob: ghcr.io/*
# no credentials block needed for public registries

Ensure the Kyverno admission controller ServiceAccount has an IAM role with ecr:GetAuthorizationToken and ecr:BatchGetImage permissions, and that AWS_ROLE_ARN / AWS_WEB_IDENTITY_TOKEN_FILE are injected into the pod. Then set providers: [amazon] on the policy:

apiVersion: policies.kyverno.io/v1
kind: ImageValidatingPolicy
metadata:
name: verify-ecr-images
spec:
matchImageReferences:
- glob: '123456789.dkr.ecr.eu-central-1.amazonaws.com/*'
credentials:
providers:
- amazon
attestors:
- name: cosign
cosign:
key:
data: |
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
validations:
- expression: >-
images.containers.map(image, verifyImageSignatures(image, [attestors.cosign])).all(e, e > 0)
message: image must be signed

Example: private registry with a pull secret

Section titled “Example: private registry with a pull secret”

The secret must be created in the Kyverno namespace before referencing it here:

Terminal window
kubectl create secret docker-registry my-registry-secret \
--docker-server=registry.example.com \
--docker-username=<user> \
--docker-password=<token> \
-n kyverno
apiVersion: policies.kyverno.io/v1
kind: ImageValidatingPolicy
metadata:
name: verify-private-images
spec:
matchImageReferences:
- glob: 'registry.example.com/*'
credentials:
secrets:
- my-registry-secret # must exist in the Kyverno namespace
attestors:
- name: notary
notary:
certs:
value: |
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
validations:
- expression: >-
images.containers.map(image, verifyImageSignatures(image, [attestors.notary])).all(e, e > 0)
message: image must be signed

Both mechanisms can be used together when a policy needs to verify images from multiple registries requiring different auth methods:

apiVersion: policies.kyverno.io/v1
kind: ImageValidatingPolicy
metadata:
name: check-images
spec:
matchImageReferences:
- glob: '*.dkr.ecr.*.amazonaws.com/*'
- glob: 'registry.example.com/*'
credentials:
providers:
- amazon
secrets:
- my-registry-secret # must exist in the Kyverno namespace

Kyverno enhances Kubernetes’ CEL environment with libraries enabling complex policy logic and advanced features for image validation. In addition to common Kyverno CEL Libraries the following additional libraries are supported for ImageValidatingPolicy types.

Kyverno provides specialized functions for verifying image signatures and attestations:

CEL ExpressionPurpose
images.containersRetrieves all container images in the resource
verifyImageSignatures(image, [attestors.notary])Verify image signatures using specified attestors
verifyAttestationSignatures(image, attestations.sbom, [attestors.notary])Verify attestation signatures for specific metadata
extractPayload(image, attestations.sbom).bomFormat == 'CycloneDX'Extract the in-toto payload

The following policy demonstrates the use of these functions:

apiVersion: policies.kyverno.io/v1
kind: ImageValidatingPolicy
metadata:
name: check-images
spec:
matchConstraints:
resourceRules:
- apiGroups: ['']
apiVersions: ['v1']
operations: ['CREATE']
resources: ['pods']
matchImageReferences:
- glob: ghcr.io/*
attestors:
- name: notary
notary:
certs:
value: |-
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
attestations:
- name: sbom
referrer:
type: sbom/cyclone-dx
validations:
- expression: >-
images.containers.map(image, verifyImageSignatures(image, [attestors.notary])).all(e, e > 0)
message: failed to verify image with notary cert
- expression: >-
images.containers.map(image, verifyAttestationSignatures(image,
attestations.sbom, [attestors.notary])).all(e, e > 0)
message: failed to verify attestation with notary cert
- expression: >-
images.containers.map(image, extractPayload(image, attestations.sbom).bomFormat == 'CycloneDX').all(e, e)
message: sbom is not a cyclone dx sbom

This policy ensures that:

  1. All images are signed by the specified notary attestor
  2. All images have valid SBOM attestations
  3. All SBOMs are in CycloneDX format

ImageValidatingPolicy comes in both cluster-scoped and namespaced versions:

  • ImageValidatingPolicy: Cluster-scoped, applies to image verification across all namespaces
  • NamespacedImageValidatingPolicy: Namespace-scoped, applies image verification only to resources within the same namespace

Both policy types have identical functionality and field structure. The only difference is the scope of resource selection.

Example NamespacedImageValidatingPolicy

apiVersion: policies.kyverno.io/v1
kind: NamespacedImageValidatingPolicy
metadata:
name: verify-team-images
namespace: development
spec:
matchConstraints:
resourceRules:
- apiGroups: ['']
apiVersions: ['v1']
operations: [CREATE, UPDATE]
resources: [pods]
matchImageReferences:
- glob: 'ghcr.io/myorg/dev-*'
attestors:
- name: dev-attestor
cosign:
keyless:
identities:
- issuer: 'https://token.actions.githubusercontent.com'
subject: '*@myorg.github.io'
validations:
- message: 'image must be signed by the development team'
expression: 'imageverify.verify(images.containers, attestors.devAttestor).all(result, result.verified)'

The NamespacedImageValidatingPolicy allows namespace owners to manage image verification policies without requiring cluster-admin permissions. This enables development teams to enforce their own image security requirements, such as verifying images are signed by their CI/CD pipeline, without affecting other namespaces or requiring cluster-level access.

This sample policy demonstrates how to verify container image signatures using Cosign keyless signing and validate the presence of a vulnerability scan attestation.

Kyverno supports the use of regular expressions in identities.subjectRegExp and identities.issuerRegExp fields when configuring keyless attestors

apiVersion: policies.kyverno.io/v1
kind: ImageValidatingPolicy
metadata:
name: require-vulnerability-scan
spec:
validationActions: [Audit]
webhookConfiguration:
timeoutSeconds: 15
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: ['']
apiVersions: ['v1']
resources: ['pods']
operations: ['CREATE', 'UPDATE']
matchImageReferences:
- glob: 'ghcr.io/myorg/myrepo:*'
attestors:
- name: cosign
cosign:
keyless:
identities:
- subject: 'https://github.com/myorg/myrepo/.github/workflows/*'
issuer: 'https://token.actions.githubusercontent.com'
ctlog:
url: 'https://rekor.sigstore.dev'
attestations:
- name: cosignAttestation
intoto:
type: cosign.sigstore.dev/attestation/vuln/v1
validations:
- expression: >-
images.containers.map(image, verifyImageSignatures(image, [attestors.cosign])).all(e, e > 0)
message: 'Failed image signature verification'
- expression: >-
images.containers.map(image, verifyAttestationSignatures(image, attestations.cosignAttestation, [attestors.cosign])).all(e, e > 0)
message: 'Failed to verify vulnerability scan attestation with Cosign keyless'

This policy ensures that container images are signed with a specified Cosign public key before being admitted.

apiVersion: policies.kyverno.io/v1
kind: ImageValidatingPolicy
metadata:
name: verify-image-ivpol
spec:
webhookConfiguration:
timeoutSeconds: 15
evaluation:
background:
enabled: false
validationActions: [Deny]
matchConstraints:
resourceRules:
- apiGroups: ['']
apiVersions: ['v1']
operations: ['CREATE', 'UPDATE']
resources: ['pods']
matchImageReferences:
- glob: 'docker.io/kyverno/kyverno*'
attestors:
- name: cosign
cosign:
key:
data: |
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6QsNef3SKYhJVYSVj+ZfbPwJd0pv
DLYNHXITZkhIzfE+apcxDjCCkDPcJ3A3zvhPATYOIsCxYPch7Q2JdJLsDQ==
-----END PUBLIC KEY-----
validations:
- expression: >-
images.containers.map(image, verifyImageSignatures(image, [attestors.cosign])).all(e ,e > 0)
message: >-
failed the image Signature verification

Note: To learn how to sign container images using Cosign with keyless signing, refer to the official Cosign documentation. Policies are applied cluster-wide to help secure your cluster. However, there may be times when teams or users need to test specific tools or resources. In such cases, users can use PolicyException to bypass policies without modifying or tweaking the policies themselves. This ensures that your policies remain secure while providing the flexibility to bypass them when needed.

For private Sigstore deployments, keyless verification can use a custom trusted root instead of the system roots. This is useful when the Fulcio, Rekor, or CT log trust material is managed internally and stored in a ConfigMap or other source accessible through CEL.

apiVersion: policies.kyverno.io/v1
kind: ImageValidatingPolicy
metadata:
name: verify-build-metadata
spec:
matchConstraints:
resourceRules:
- apiGroups: ['']
apiVersions: ['v1']
operations: ['CREATE', 'UPDATE']
resources: ['pods']
matchImageReferences:
- glob: 'ghcr.io/nirmata/demo-private-attestations*'
variables:
- name: githubTrustedRoot
expression: >-
resource.get("v1", "configmaps", "kyverno", "github-trusted-root").data["trusted_root.json"]
attestations:
- name: buildMeta
intoto:
type: 'https://nirmata.com/attestations/build-metadata/v1'
attestors:
- name: github
cosign:
keyless:
identities:
- issuer: 'https://token.actions.githubusercontent.com'
subjectRegExp: '^https://github\\.com/nirmata/demo-attestations-kyverno/\\.github/workflows/reusable-build-attest\\.yml@refs/(heads/main|tags/v.*)$'
trustedRoot:
expression: variables.githubTrustedRoot
ctlog:
insecureIgnoreTlog: true
insecureIgnoreSCT: true
validations:
- expression: >-
images.containers.map(image,
verifyAttestationSignatures(image, attestations.buildMeta, [attestors.github]) > 0
? extractPayload(image, attestations.buildMeta)
: dyn(null)).all(p, p != null)
message: 'Failed to verify build-metadata attestation'

The trustedRoot field lets you provide the full Sigstore trusted root JSON to the keyless verifier. In the example above, the trusted root is read from a ConfigMap and passed into the attestor through CEL.