CKSKubernetes 1.35

Lessons

SBOM, signing, and registry allow-listing

updated 2026-10-22 by seed

The three questions of supply chain security

  1. What is in this artifact? → SBOM (software bill of materials)
  2. Who built it and has it been changed? → signature / attestation
  3. Is this registry allowed at all? → admission policy

1. SBOM: what is inside

bom is allow-listed in the exam — a strong hint.

bom generate --image registry.example.com/app:1.4.2 -o sbom.spdx
bom document outline sbom.spdx          # human-readable package list
bom document query sbom.spdx 'name:openssl'   # is a package in there?

Alternatives: syft registry.example.com/app:1.4.2 -o spdx-json, and Trivy can
emit one too (trivy image -f spdx-json).

Why it matters for the exam: when a CVE drops in a library, the SBOM tells you
which images are affected without pulling every image and scanning it.

2. Signing and verifying

# key pair
cosign generate-key-pair

# sign by digest (never by tag)
cosign sign --key cosign.key registry.example.com/app@sha256:<digest>

# verify
cosign verify --key cosign.pub registry.example.com/app:1.4.2
cosign verify-attestation --type spdxjson --key cosign.pub registry.example.com/app:1.4.2

cosign verify fails with no matching signatures for an unsigned or tampered
image. Keyless (Fulcio/Rekor) exists and is what real pipelines use; the exam
gives you a key pair.

3. Enforce: three ways, all valid

Gatekeeper

spec:
  match:
    kinds: [{apiGroups: [""], kinds: ["Pod"]}]
  parameters:
    repos: ["registry.example.com/", "registry.k8s.io/"]

Kyverno

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata: {name: allowed-registries}
spec:
  validationFailureAction: Enforce
  rules:
  - name: check-registry
    match:
      any:
      - resources: {kinds: ["Pod"]}
    validate:
      message: "images must come from registry.example.com"
      pattern:
        spec:
          containers:
          - image: "registry.example.com/*"

ImagePolicyWebhook — the one people skip, and the exam likes

The API server calls your HTTP service and asks allow/deny. Three pieces:

a. The admission config (/etc/kubernetes/admission/admission-config.yaml):

apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: ImagePolicyWebhook
  configuration:
    imagePolicy:
      kubeConfigFile: /etc/kubernetes/admission/imagepolicy.kubeconfig
      allowTTL: 50
      denyTTL: 50
      retryBackoff: 500
      defaultAllow: false        # fail closed — the whole point

b. The kubeconfig that points at the webhook (+ its client cert), mounted
into the API server.

c. The flags on the API server:

    - --enable-admission-plugins=NodeRestriction,ImagePolicyWebhook
    - --admission-control-config-file=/etc/kubernetes/admission/admission-config.yaml

Then a Pod using a non-allowed image is rejected with
image policy webhook backend denied the request.

defaultAllow: false means "if the webhook cannot be reached, deny". Set it to
true and you have a policy that disappears the moment the backend is down.

4. Registry hygiene that is not admission

  • Pull secret instead of a public repo: `kubectl create secret docker-registry regcred --docker-server=… --docker-username=… --docker-password=… + imagePullSecrets`.
  • imagePullPolicy: Always for mutable tags, IfNotPresent with a digest.
  • An artifact repository in front of the internet (Harbor, Artifactory, ECR) — "understand your supply chain" means knowing this layer exists.

What the marker checks

  • the image reference in the running Pod (digest? allowed registry?)
  • the policy object exists and is enforced (a violation actually rejected)
  • for signing tasks: cosign verify output, or the admission denial message

Practical order under time pressure: allow-list first (cheapest), scanning second
(finds the obvious), signing last (needs keys and a registry that stores them).