Supply Chain Security
This guide shows you how to:
- Create a Pipeline to build and push a container image to a local registry.
- Record and sign provenance of the image.
- Read back the provenance information.
- Verify the signature.
Prerequisites
- Install minikube. Only complete the step 1, “Installation”.
- Install kubectl.
- Install tkn, the Tekton CLI.
- Install jq.
- Install cosign.
Start minikube with a local registry enabled
Delete any previous clusters:
minikube deleteStart up minikube with insecure registry enabled:
minikube start --insecure-registry "10.0.0.0/24"The process takes a few seconds, you see an output similar to the following, depending on the minikube driver that you are using:
😄 minikube v1.36.0 on Darwin 15.5 (arm64) ✨ Using the qemu2 driver based on existing profile 👍 Starting "minikube" primary control-plane node in "minikube" cluster 🏃 Updating the running qemu2 "minikube" VM ... 📦 Preparing Kubernetes v1.33.1 on containerd 1.7.23 ... 🔗 Configuring bridge CNI (Container Networking Interface) ... 🔎 Verifying Kubernetes components... ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5 🌟 Enabled addons: default-storageclass, storage-provisioner 🏄 Done! kubectl is now configured to use "minikube" cluster and "default" namespace by defaultEnable the local registry plugin:
minikube addons enable registryThe output confirms that the registry plugin is enabled:
💡 registry is an addon maintained by minikube. For any concerns contact minikube on GitHub. You can view the list of minikube maintainers at: https://github.com/kubernetes/minikube/blob/master/OWNERS ▪ Using image gcr.io/k8s-minikube/kube-registry-proxy:0.0.9 ▪ Using image docker.io/registry:3.0.0 🔎 Verifying registry addon... 🌟 The 'registry' addon is enabled
Now you can push images to a registry within your minikube cluster.
Install and configure the necessary Tekton components
Install Tekton Pipelines:
kubectl apply --filename \ https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yamlMonitor the installation:
kubectl get po -n tekton-pipelines -wWhen both
tekton-pipelines-controllerandtekton-pipelines-webhookshow1/1under theREADYcolumn, you are ready to continue. For example:NAME READY STATUS RESTARTS AGE tekton-events-controller-786b59d5cd-jt7d9 1/1 Running 0 2m tekton-pipelines-controller-59b6cdbbc-2kw2w 1/1 Running 0 2m tekton-pipelines-webhook-74b5cdfcc4-g4qj2 1/1 Running 0 2mHit Ctrl + C to stop monitoring.
Install Tekton Chains:
kubectl apply --filename \ https://storage.googleapis.com/tekton-releases/chains/latest/release.yamlMonitor the installation
kubectl get po -n tekton-chains -wWhen
tekton-chains-controllershows1/1under theREADYcolumn, you are ready to continue. For example:NAME READY STATUS RESTARTS AGE tekton-chains-controller-7dccbf8fc7-9wdkl 1/1 Running 0 38sHit Ctrl + C to stop monitoring.
Configure Tekton Chains to store the provenance metadata locally:
kubectl patch configmap chains-config -n tekton-chains \ -p='{"data":{"artifacts.oci.storage": "", "artifacts.taskrun.format":"in-toto", "artifacts.taskrun.storage": "tekton"}}'The output confirms that the configuration was updated successfully:
configmap/chains-config patchedGenerate a key pair to sign the artifact provenance:
cosign generate-key-pair k8s://tekton-chains/signing-secretsYou are prompted to enter a password for the private key. For this guide, leave the password empty and press Enter twice. A public key,
cosign.pub, is created in your current directory.
Build and push a container image
Create a file called
pipeline.yamland add the following:Get your cluster IPs:
kubectl get service --namespace kube-systemThis shows the IPs of the services on your cluster:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 48m registry ClusterIP 10.101.134.48 <none> 80/TCP,443/TCP 47mSave your registry IP, in this case
10.101.134.48, for the next step.Create a file called
pipelinerun.yamland add the following:Replace
<registry-ip>with the value from the previous step.Apply the Pipeline to your cluster:
kubectl apply -f pipeline.yamlYou see the following output:
pipeline.tekton.dev/build-push created task.tekton.dev/create-dockerfile created task.tekton.dev/kaniko createdRun the Pipeline:
kubectl create -f pipelinerun.yamlA new PipelineRun with a unique name is created:
pipelinerun.tekton.dev/build-push-run-q22b5 createdMonitor the execution:
tkn pr logs --last -fThe output shows the Pipeline completed successfully:
[kaniko-build : build-and-push] 2025/07/21 22:19:13 ERROR failed to get CPU variant os=linux error="getCPUVariant for OS linux: not implemented" [kaniko-build : build-and-push] INFO[0000] Retrieving image manifest alpine:3.22 [kaniko-build : build-and-push] INFO[0000] Retrieving image alpine:3.22 from registry index.docker.io [kaniko-build : build-and-push] INFO[0002] Built cross stage deps: map[] [kaniko-build : build-and-push] INFO[0002] Retrieving image manifest alpine:3.22 [kaniko-build : build-and-push] INFO[0002] Returning cached image manifest [kaniko-build : build-and-push] INFO[0002] Executing 0 build triggers [kaniko-build : build-and-push] INFO[0002] Building stage 'alpine:3.22' [idx: '0', base-idx: '-1'] [kaniko-build : build-and-push] INFO[0002] Unpacking rootfs as cmd RUN echo "hello world" > hello.log requires it. [kaniko-build : build-and-push] INFO[0005] RUN echo "hello world" > hello.log [kaniko-build : build-and-push] INFO[0005] Initializing snapshotter ... [kaniko-build : build-and-push] INFO[0005] Taking snapshot of full filesystem... [kaniko-build : build-and-push] INFO[0005] Cmd: /bin/sh [kaniko-build : build-and-push] INFO[0005] Args: [-c echo "hello world" > hello.log] [kaniko-build : build-and-push] INFO[0005] Running: [/bin/sh -c echo "hello world" > hello.log] [kaniko-build : build-and-push] INFO[0005] Taking snapshot of full filesystem... [kaniko-build : build-and-push] INFO[0005] Pushing image to 10.99.166.178/tekton-test [kaniko-build : build-and-push] INFO[0007] Pushed 10.99.166.178/tekton-test@sha256:3254d61ef67ceb4dd7906b14bb070c00fa039d70ccebb116f08d4f22127f1cf7 [kaniko-build : write-url] 10.99.166.178/tekton-test
Retrieve and verify the artifact provenance
Tekton Chains silently monitored the execution of the PipelineRun. It recorded and signed the provenance metadata, information about the container that the PipelineRun built and pushed.
Get the PipelineRun UID:
export PR_UID=$(tkn pr describe --last -o jsonpath='{.metadata.uid}')Fetch the metadata and store it in a JSON file:
tkn pr describe --last \ -o jsonpath="{.metadata.annotations.chains\.tekton\.dev/signature-pipelinerun-$PR_UID}" \ | base64 -d > metadata.jsonView the provenance:
cat metadata.json | jq -r '.payload' | base64 -d | jq .The output contains a detailed description of the build:
To verify that the metadata hasn’t been tampered with, check the signature with
cosign:cosign verify-blob-attestation --insecure-ignore-tlog \ --key k8s://tekton-chains/signing-secrets --signature metadata.json \ --type slsaprovenance --check-claims=false /dev/nullThe output confirms that the signature is valid:
Verified OK