Skip to content
advanced Phase 17 · Container and Kubernetes Security

Image Signing and Admission Control

Enforce that only signed, trusted images ever start in the cluster.

55m
0 problems
Topic Progress 0%

Image Signing: Proving Provenance

Image Signing: Proving Provenance

Container images are the unit of deployment in Kubernetes. When a pod starts, the kubelet pulls the image and runs it — with no verification that the image was built by your pipeline and hasn't been tampered with. Image signing closes this gap by cryptographically proving which pipeline produced an artifact.

The supply chain problem

A container image passes through many hands before it runs in production:

  1. A developer writes code and creates a pull request
  2. A CI system builds the image from the source
  3. The image is pushed to a registry
  4. Kubernetes pulls the image and runs it

At any point, an attacker can:

  • Inject malicious code into the build
  • Replace the image in the registry with a trojaned version
  • Compromise the CI system and build from modified source
  • Pull an image from a public registry that happens to have the same name

Without image signing, there is no way to verify that the image running in production is the image your pipeline built.

How image signing works

Image signing uses public-key cryptography:

  1. The build pipeline has a private key (kept secret)
  2. After building the image, the pipeline creates a signature using the private key
  3. The signature is stored alongside the image in the registry
  4. At deployment time, the cluster verifies the signature using the public key
# Sign an image with Cosign (Sigstore)
cosign sign --key cosign.key registry.company.com/myapp:v1.2.3

# Verify a signature
cosign verify --key cosign.pub registry.company.com/myapp:v1.2.3

# Output:
# Verification for registry.company.com/myapp:v1.2.3 --
# The following checks were performed on each of these signatures:
#   - The cosign claims were validated
#   - Existence of the claims in the transparency log was verified offline
#   - The signatures were verified against the specified public key

If the image is modified after signing, the signature verification fails — the cryptographic proof no longer matches the image content.

Sigstore and keyless signing

Sigstore is a project that simplifies image signing by eliminating the need to manage signing keys:

# Keyless signing — uses OIDC identity, no key management
cosign sign registry.company.com/myapp:v1.2.3

# The signature is tied to the OIDC identity (e.g., GitHub Actions workflow)
# Verification checks that the signature was created by the expected identity
cosign verify \
  --certificate-identity=https://github.com/company/repo/.github/workflows/build.yml@refs/heads/main \
  --certificate-oidc-issuer=https://token.actions.githubusercontent.com \
  registry.company.com/myapp:v1.2.3

Keyless signing uses short-lived certificates tied to an OIDC identity (like a GitHub Actions workflow). The signature proves that the image was built by a specific workflow run — no long-lived keys to manage or leak.

Signing in CI/CD

Signing should happen automatically in the build pipeline:

# GitHub Actions example
- name: Build and push
  run: |
    docker build -t registry.company.com/myapp:${{ github.sha }} .
    docker push registry.company.com/myapp:${{ github.sha }}

- name: Sign image
  uses: sigstore/cosign-installer@v3
- run: |
    cosign sign --yes \
      registry.company.com/myapp:${{ github.sha }}

The signature is created immediately after the push, ensuring every image in the registry is signed.

Admission Controllers: Enforcing Policy at Deploy Time

Admission Controllers: Enforcing Policy at Deploy Time

Signing an image proves its provenance, but something needs to verify that signature before the image runs. That's the job of admission controllers — Kubernetes interceptors that inspect (and can reject) resources before they're created.

How admission control works

When you submit a resource to the API server (e.g., kubectl apply), it goes through an admission chain:

kubectl apply → API Server → MutatingAdmissionWebhooks → ValidatingAdmissionWebhooks → etcd
  • Mutating webhooks can modify the resource (e.g., inject sidecar containers)
  • Validating webhooks can accept or reject the resource (e.g., reject pods that don't meet policy)

OPA Gatekeeper

OPA Gatekeeper is an admission controller that evaluates resources against policies written in Rego:

# Policy: reject pods that run as root
package k8spspuser

violation[msg] {
    input.review.object.spec.containers[_].securityContext.runAsNonRoot == false
    msg := "Container must not run as root"
}

violation[msg] {
    not input.review.object.spec.containers[_].securityContext.runAsNonRoot
    msg := "Container must set runAsNonRoot: true"
}
# Gatekeeper ConstraintTemplate
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8spspuser
spec:
  crd:
    spec:
      names:
        kind: K8sPSPUser
  targets:
  - target: admission.k8s.gatekeeper.sh
    rego: |
      package k8spspuser
      violation[msg] {
          input.review.object.spec.containers[_].securityContext.runAsNonRoot == false
          msg := "Container must not run as root"
      }

Kyverno

Kyverno is a policy engine designed specifically for Kubernetes. Policies are written as Kubernetes resources (no Rego required):

# Kyverno policy: require non-root containers
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-non-root
spec:
  validationFailureAction: Enforce
  rules:
  - name: check-run-as-non-root
    match:
      resources:
        kinds:
        - Pod
    validate:
      message: "Containers must run as non-root"
      pattern:
        spec:
          containers:
          - securityContext:
              runAsNonRoot: true
---
# Kyverno policy: reject privileged pods
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-privileged
spec:
  validationFailureAction: Enforce
  rules:
  - name: check-privileged
    match:
      resources:
        kinds:
        - Pod
    validate:
      message: "Privileged containers are not allowed"
      pattern:
        spec:
          containers:
          - securityContext:
              =(privileged): "false"

Common admission policies

A baseline policy set should enforce:

  1. Non-root containers: runAsNonRoot: true
  2. No privileged pods: privileged: false
  3. No host networking: hostNetwork: false
  4. No host PID/IPC: hostPID: false, hostIPC: false
  5. Read-only root filesystem: readOnlyRootFilesystem: true
  6. Dropped capabilities: capabilities.drop: [ALL]
  7. Approved registries: only images from registry.company.com
  8. Signed images: only images with valid signatures
# Kyverno: restrict to approved registries
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: restrict-registries
spec:
  validationFailureAction: Enforce
  rules:
  - name: validate-registry
    match:
      resources:
        kinds:
        - Pod
    validate:
      message: "Only images from registry.company.com are allowed"
      pattern:
        spec:
          containers:
          - image: "registry.company.com/*"

Verifying signatures in admission

With Cosign and Kyverno, you can verify image signatures at admission time:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-image-signatures
spec:
  validationFailureAction: Enforce
  rules:
  - name: check-signature
    match:
      resources:
        kinds:
        - Pod
    verifyImages:
    - imageReferences:
      - "registry.company.com/*"
      attestors:
      - entries:
        - keys:
            publicKeys: |
              -----BEGIN PUBLIC KEY-----
              MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
              -----END PUBLIC KEY-----

This policy rejects any pod that uses an image from registry.company.com without a valid signature from the expected public key.

Practice Problems

0 / 2 solved
Write a Kyverno Policy for Security Baselines

Write a Kyverno ClusterPolicy that enforces the following on all pods: (a) run as non-root, (b) drop all capabilities, (c) use a read-only root filesystem, (d) prevent privilege escalation.

Solution
```yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: pod-security-baseline
spec:
  validationFailureAction: Enforce
  background: false
  rules:
  - name: require-non-root
    match:
      resources:
        kinds: [Pod]
    validate:
      message: "Containers must run as non-root"
      pattern:
        spec:
          containers:
          - securityContext:
              runAsNonRoot: true
  - name: drop-all-capabilities
    match:
      resources:
        kinds: [Pod]
    validate:
      message: "All capabilities must be dropped"
      pattern:
        spec:
          containers:
          - securityContext:
              capabilities:
                drop: [ALL]
  - name: require-readonly-rootfs
    match:
      resources:
        kinds: [Pod]
    validate:
      message: "Root filesystem must be read-only"
      pattern:
        spec:
          containers:
          - securityContext:
              readOnlyRootFilesystem: true
  - name: disallow-privilege-escalation
    match:
      resources:
        kinds: [Pod]
    validate:
      message: "Privilege escalation must be disabled"
      pattern:
        spec:
          containers:
          - securityContext:
              allowPrivilegeEscalation: false
```
Implement Image Signing in CI/CD

Describe how to implement image signing in a CI/CD pipeline using Cosign keyless signing. Include the build, push, and verification steps.

Quiz

1. What is the purpose of image signing in a container supply chain?

Question 1 options

2. What is the difference between a mutating and a validating admission webhook?

Question 2 options

3. Why is keyless signing (Sigstore) easier to manage than traditional key-based signing?

Question 3 options

Flashcards

Question

What does image signing prove?

Answer

It cryptographically proves which pipeline produced a container image and that the image hasn't been tampered with since it was signed.

Question

What is the difference between mutating and validating admission webhooks?

Answer

Mutating webhooks can modify resources before creation (e.g., inject sidecars). Validating webhooks can only accept or reject resources — they cannot modify them.

Question

What is keyless signing and why is it easier to manage?

Answer

Keyless signing uses short-lived certificates tied to OIDC identities instead of long-lived private keys. No key management, rotation, or leak risk.

Question

What are the essential admission control policies for pod security?

Answer

Non-root containers, no privileged pods, no host networking, read-only root filesystem, dropped capabilities, approved registries, and signed images.

Revision Notes

Key Takeaways

  • 1. Image signing provides cryptographic provenance — proof of which pipeline built an image
  • 2. Admission controllers enforce policy at deploy time, rejecting non-compliant resources before they run
  • 3. Keyless signing eliminates key management by using OIDC identities
  • 4. A baseline policy set should enforce non-root, no privileged, read-only filesystem, dropped capabilities, and signed images

Interview Tips

  • • Use a concrete example: 'We sign every image in CI with Cosign keyless signing, and Kyverno verifies the signature at admission — unsigned images are rejected.'
  • • Explain the supply chain protection: 'Even if an attacker compromises the registry, they can't deploy a trojaned image because it won't have a valid signature from our pipeline.'
  • • Discuss the policy approach: 'We use Kyverno for admission policies because they're Kubernetes-native — our team doesn't need to learn Rego.'

Cheat Sheet

Image Signing and Admission Control Cheat Sheet

Phase: Container and Kubernetes Security | Level: advanced

Image signing:

  • Proves which pipeline produced an image and detects tampering
  • Uses public-key cryptography: private key signs, public key verifies
  • Keyless signing (Sigstore): short-lived certificates tied to OIDC identities — no key management
  • Sign in CI/CD immediately after push

Admission controllers:

  • Intercept resources before they're created in etcd
  • Mutating webhooks: can modify resources
  • Validating webhooks: can accept or reject only
  • Tools: OPA Gatekeeper (Rego policies), Kyverno (Kubernetes-native policies)

Essential policies:

  • Non-root containers (runAsNonRoot: true)
  • No privileged pods (privileged: false)
  • No host networking/PID/IPC
  • Read-only root filesystem
  • Drop all capabilities
  • Approved registries only
  • Signed images only

Kyverno vs. Gatekeeper:

  • Kyverno: policies as Kubernetes resources, no new language to learn
  • Gatekeeper: Rego policies, more powerful but steeper learning curve

Key pattern:

Build → Push → Sign → Admission verifies signature → Pod runs

Continue Learning

Before You Start

  • No prerequisites listed.