TT Lab
Get started
Learn Learning paths Courses

CKS — Kubernetes Security Specialist

Pinning Images and Controlling Provenance

Continue in TT Lab

Goal

You build the standard configuration that pins images entering the cluster by digest, narrows their origin to an allowlist, and handles private registry authentication at the ServiceAccount level.

Why it matters

A tag is a mutable pointer. A different image can be pushed to the same v1.2 tag, and then the image you verified yesterday and the image running today may differ. A digest is a hash of the content, so it eliminates this problem at the root. Whether you can instantly answer "what exactly is running right now" when an incident occurs determines your response time.

Origin control comes next. The allowed registry list goes straight into the policy engine as a parameter, and imagePullSecrets is the credential for connecting to that registry. If you attach it to a ServiceAccount rather than writing it on each Pod, new workloads inherit it automatically, so omissions decrease.

This environment has no internet, so it can't actually pull images or install policy engine CRDs like Kyverno. So the policy manifest is written as a file and graded, and the rest is graded as real objects.

Steps

  1. Create the namespace cks-supply and create a Pod pinned in it. Pin the image by digest with no tag (registry.cks.local/shop/api@sha256: followed by 64 hex digits). imagePullPolicy is IfNotPresent.
  2. In /root/cks-supply-chain/allowed-registries.txt, write the allowed registry prefixes, one per line, 4 lines in exactly the order below. registry.cks.local/, harbor.cks.local/library/, registry.k8s.io/, ghcr.io/cks-lab/
  3. Create a Secret harbor-pull of type kubernetes.io/dockerconfigjson in cks-supply. The server is harbor.cks.local, the user is robot$cks, and the password is cks-lab-token.
  4. Create a ServiceAccount deployer in cks-supply and put harbor-pull in its imagePullSecrets.
  5. Write a signature verification policy manifest in /root/cks-supply-chain/verify-images.yaml. apiVersion: kyverno.io/v1, kind: ClusterPolicy, the name is verify-image-signature, spec.validationFailureAction is Enforce, and one rule (spec.rules[0].name is check-signature) targets registry.cks.local/* with verifyImages[0].imageReferences[0].
  6. Create a Pod legacy-web (image nginx:latest) in cks-supply to reproduce an unpinned workload. Then write a detection script in /root/cks-supply-chain/find-unpinned.sh and save its output to /root/cks-supply-chain/unpinned.txt. The output holds the Pods in the cks-supply namespace that have a container whose image is not pinned with @sha256:, in the format cks-supply/<파드이름> (the placeholder is the Pod name), sorted alphabetically, one per line.
  7. Create a Deployment checkout in cks-supply. Replicas 2, the selector and Pod label are app=checkout, serviceAccountName is deployer, the container name is app, the image is one that starts with registry.cks.local/ and is pinned with @sha256:, and imagePullPolicy is IfNotPresent.

Notes

Pin by digest instead of tag

Create the namespace cks-supply and create a Pod pinned in it. Pin the image by digest with no tag (registry.cks.local/shop/api@sha256: followed by 64 hex digits). imagePullPolicy is IfNotPresent.

An image reference has the form <레지스트리>/<이름>@sha256:<64자리 16진수> (the placeholders are the registry, the name, and the 64 hex digits). Don't use a tag and a digest together; keep only the digest.

The allowed registry list

In /root/cks-supply-chain/allowed-registries.txt, write the allowed registry prefixes, one per line, 4 lines in exactly the order below. registry.cks.local/, harbor.cks.local/library/, registry.k8s.io/, ghcr.io/cks-lab/

This is a list that will be passed straight through as a policy engine parameter. Write it in prefix form, and add the trailing slash so that other registries with similar names don't pass.

A registry authentication Secret

Create a Secret harbor-pull of type kubernetes.io/dockerconfigjson in cks-supply. The server is harbor.cks.local, the user is robot$cks, and the password is cks-lab-token.

One line of kubectl create secret docker-registry is enough. The type name is kubernetes.io/dockerconfigjson and the data key is .dockerconfigjson.

Attach the pull Secret to a ServiceAccount

Create a ServiceAccount deployer in cks-supply and put harbor-pull in its imagePullSecrets.

If you put it in the ServiceAccount's imagePullSecrets, every Pod that uses that SA authenticates automatically. It is better than writing it on each Pod.

A signature and SBOM verification policy manifest

Write a signature verification policy manifest in /root/cks-supply-chain/verify-images.yaml. apiVersion: kyverno.io/v1, kind: ClusterPolicy, the name is verify-image-signature, spec.validationFailureAction is Enforce, and one rule (spec.rules[0].name is check-signature) targets registry.cks.local/* with verifyImages[0].imageReferences[0].

This cluster has no policy engine CRDs, so you write it only as a file. The structure is that imageReferences selects the target registry and you put the public key in attestors.

Detect Pods not pinned by digest

Create a Pod legacy-web (image nginx:latest) in cks-supply to reproduce an unpinned workload. Then write a detection script in /root/cks-supply-chain/find-unpinned.sh and save its output to /root/cks-supply-chain/unpinned.txt. The output holds the Pods in the cks-supply namespace that have a container whose image is not pinned with @sha256:, in the format cks-supply/<파드이름> (the placeholder is the Pod name), sorted alphabetically, one per line.

If the image string has no @sha256:, it is unpinned. You can filter with jq's contains or test, and you must look at init containers as well.

Putting it together: a deployment with pinning, authentication, and origin

Create a Deployment checkout in cks-supply. Replicas 2, the selector and Pod label are app=checkout, serviceAccountName is deployer, the container name is app, the image is one that starts with registry.cks.local/ and is pinned with @sha256:, and imagePullPolicy is IfNotPresent.

Use the ServiceAccount you created earlier and the first prefix of the allowed registry list as they are. Pin the image by digest and specify imagePullPolicy as well.