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.
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](/docs/hetzner/apalla/security/verify-node-integrity) for the node side.
## Prerequisites
- `kubectl` access 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](/docs/hetzner/apalla/security/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](/docs/hetzner/apalla/security/control-admission) 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.
```yaml
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:
```yaml
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](/docs/hetzner/apalla/security/scan-workload-images) 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](/docs/hetzner/apalla/security/use-kyverno-policies) for installation and high-availability configuration.
Example policy (keyless, using GitHub Actions as the signing identity):
```yaml
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](/docs/hetzner/apalla/security/use-kyverno-policies) for a working example.
## Verify
```console
# A signed image should be admitted:
$ kubectl -n team-a run signed --image=registry.example.com/app@sha256:
# 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](/docs/hetzner/apalla/security/use-kyverno-policies): installation, `failurePolicy` wiring, and high-availability setup.
- [Control admission](/docs/hetzner/apalla/security/control-admission): the webhook-guard contract and when to use a native ValidatingAdmissionPolicy.
- [Verify node integrity](/docs/hetzner/apalla/security/verify-node-integrity): dm-verity and SBOM for the node image.
- [Security architecture](/docs/hetzner/apalla/security/security-architecture): the full workload security checklist.
- [Segment with network policies](/docs/hetzner/apalla/security/segment-with-network-policies): adding network controls alongside image-signature checks.