Verify image signatures
By default, any container image is admitted to your workload clusters. Add an admission check to require that images are cryptographically signed before pods can run.
Note
Syself's node image has its own integrity story: dm-verity and a published SBOM. This page covers your workload images, not the node image. See Verify node integrity for the node side.
Prerequisites#
kubectlaccess to the workload cluster- Your images are signed with cosign or another sigstore-compatible tool
- Kyverno installed if you use Option A (see Use Kyverno policies )
How image-signature checking works#
When a pod is created, an admission controller (a plugin that inspects resources before Kubernetes accepts them) checks the container images and rejects the pod if a signature is missing or invalid.
On Syself, set failurePolicy: Ignore on any admission controller you deploy for this check. This is a recommendation, not something webhook-guard enforces: webhook-guard restricts which API groups and resources a webhook can target, it does not check a webhook's failurePolicy. With failurePolicy: Ignore, if the verifier is down or unreachable, the API server skips it and admits the pod. Image-signature checking is therefore best-effort. Run the verifier highly available to make the check reliable in practice.
Why a native ValidatingAdmissionPolicy does not work here#
A native ValidatingAdmissionPolicy (VAP) runs inside the API server and can hard-block pods without the failurePolicy: Ignore limitation. For many rules, a VAP is the right tool.
Signature verification is the exception. A VAP can inspect the image string: registry, repository, tag, or digest. It cannot fetch a signature from a registry and verify it against a key. That requires network calls and cryptography that a VAP does not support.
Use a VAP to require that images come from a specific registry or use a digest. Use a webhook-based tool (below) to require that images are signed.
Restrict image sources and tags#
Signature verification proves who built an image. It does not stop a pod from pulling nginx:latest from a public registry. Add a second, cheaper check that runs inside the API server with a ValidatingAdmissionPolicy (VAP): require every image to come from an allowed registry or be pinned to a digest, and reject the :latest tag. A VAP only reads the image string, so it needs no network calls and can hard-block without the failurePolicy: Ignore limitation.
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: restrict-image-sources
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
validations:
- expression: >-
object.spec.containers.all(c,
c.image.contains('@sha256:') || c.image.startsWith('registry.example.com/'))
message: "images must come from registry.example.com or be pinned to a @sha256 digest"
- expression: "object.spec.containers.all(c, !c.image.endsWith(':latest'))"
message: "the :latest tag is not allowed; pin a version tag or a digest"
Bind it to your own namespaces so it does not touch platform workloads:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: restrict-image-sources
spec:
policyName: restrict-image-sources
validationActions: [Deny]
matchResources:
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: team-a
The expressions above cover containers only. Extend them to object.spec.initContainers and object.spec.ephemeralContainers if your pods use them.
Signing is not scanning. A signature proves who built an image, not what is inside it. Scan your workload images for known CVEs as well, in CI before you push and again inside the cluster; Scan workload images for vulnerabilities is the how. The platform SBOM covers the node image only, never the contents of your workload images, so that side is yours.
Require signatures with a webhook verifier#
Two webhook-based tools can require signatures on this platform. Both run with failurePolicy: Ignore, since Syself does not enforce this setting for you.
Kyverno's verifyImages rule performs cosign (a tool for signing and verifying container images) signature verification. It runs as a webhook. Set failurePolicy: Ignore on it, since Syself does not enforce this setting for you.
See Use Kyverno policies for installation and high-availability configuration.
Example policy (keyless, using GitHub Actions as the signing identity):
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-signed-images
spec:
validationFailureAction: Enforce # block pods that fail the rule when Kyverno is up
webhookConfiguration:
failurePolicy: Ignore # recommended so a verifier outage does not block the API server
rules:
- name: verify-signature
match:
any:
- resources:
kinds: [Pod]
namespaces: [team-a] # scope to your namespaces
verifyImages:
- imageReferences: ["registry.example.com/*"]
attestors:
- entries:
- keyless:
subject: "https://github.com/your-org/*"
issuer: "https://token.actions.githubusercontent.com"
The sigstore policy-controller is a purpose-built admission controller for image signatures and attestations. It is also webhook-based. Configure it with failurePolicy: Ignore and run it highly available.
Roll out safely#
Scope the policy to your own namespaces. Do not target kube-system or platform namespaces; Syself manages those images. First set validationFailureAction: Audit. This logs which pods would be blocked without actually blocking them. Once you confirm your signed images pass, switch to Enforce. Because the webhook uses failurePolicy: Ignore, it only protects you while it is running. Keep it running by deploying several replicas and adding a PodDisruptionBudget (a rule that keeps a minimum number of replicas up during node drains). See Use Kyverno policies for a working example.
Verify#
# A signed image should be admitted:
$ kubectl -n team-a run signed --image=registry.example.com/app@sha256:<signed-digest>
# An unsigned image should be blocked by your policy:
$ kubectl -n team-a run unsigned --image=docker.io/library/busybox # expect: denied
Known limits#
Best-effort under Ignore. If the verifier is down when a pod is created, an unsigned image can be admitted. High-availability mode with alerting on verifier health is the mitigation.
Workload images only. This policy covers images you deploy. The node image integrity story is separate and handled by the platform.
Related#
- Use Kyverno policies : installation,
failurePolicywiring, and high-availability setup. - Control admission : the webhook-guard contract and when to use a native ValidatingAdmissionPolicy.
- Verify node integrity : dm-verity and SBOM for the node image.
- Security architecture : the full workload security checklist.
- Segment with network policies : adding network controls alongside image-signature checks.
Supply-chain security
How the Syself Linux node image is built from hash-pinned sources with a reproducible seal, what the SPDX SBOM and VEX verdicts let you verify, and exactly what is and is not signed or attested.
Scan workload images for vulnerabilities
Scan the images your workloads pull for known CVEs with Trivy or Grype, in CI and again inside the cluster, and gate on the results.