Build System Provenance Verification
In the SLSA provenance, there is a builder.id
field, which is used to indicate the build environment of the image.
In this document, we will use this builder.id
field to verify the image.
TIP
Since Tekton Chains has already handled both image signing and SLSA provenance generation in the preparation stage, we can directly reuse the process and images from Quick Start: Signed Provenance.
We will focus on verifying the SLSA provenance in this document.
TOC
Feature Overview
This method uses Chains to automatically generate SLSA Provenance for the built image and then use Kyverno to verify the provenance:
- Configure Tekton Chains to automatically generate SLSA Provenance for the built image.
- Use
buildah
Tekton Task to build the image.
- (Optional) Use
cosign
cli to verify the attestation.
- Configure Kyverno rules to verify the attestation.
- Use the image to create a Pod to verify the attestation.
Use Cases
The following scenarios require referring to the guidance in this document:
- Verifying the build environment of container images using SLSA provenance
- Implementing build system provenance verification using CUE or Rego policies
- Enforcing security policies to only allow images built in specific build environments
- Setting up automated build system provenance verification in CI/CD pipelines
- Ensuring build system provenance integrity and authenticity in production environments
Prerequisites
- A Kubernetes cluster with Tekton Pipelines, Tekton Chains and Kyverno installed
- A registry with image pushing enabled
kubectl
CLI installed and configured to access your cluster
cosign
CLI tool installed
jq
CLI tool installed
Process Overview
Step | Operation | Description |
---|
1 | Generate signing keys | Create a key pair for signing artifacts using cosign |
2 | Set up authentication | Configure registry credentials for image pushing |
3 | Configure Tekton Chains | Set up Chains to use OCI storage and configure signing |
4 | Create a sample pipeline | Create a pipeline definition with necessary tasks and workspaces |
5 | Run a sample pipeline | Create and run a PipelineRun with proper configuration |
6 | Wait for signing | Wait for the PipelineRun to be signed by Chains |
7 | Get image information | Extract image URI and digest from the PipelineRun |
8 | (Optional) Verify signatures with cosign | Verify the image attestation using cosign CLI |
9 | Verify signatures with Kyverno | Configure and verify the image attestation using Kyverno policies |
10 | Clean up resources | Delete the test Pods and policies |
Step-by-Step Instructions
Steps 1-7: (Optional) Basic Setup
NOTE
If you change the builder.id
field, you need to re-run the pipeline to generate the image.
Because the old image is not signed with the new builder.id
, so it will be blocked by the policy.
Otherwise, you can skip this step, use the old image to verify the policy.
These steps are identical to the Quick Start: Signed Provenance guide. Please follow the instructions in that guide for:
Step 8: (Optional) Verify the builder info with cosign
TIP
This step is optional and should be performed when you need to verify the authenticity of the image builder by cosign.
If you interested how to use CUE
or Rego
to verify the builder info, you can continue to read the following content.
Get the signing public key according to the Get the signing public key section.
Cosign provides two ways to validate the attestation:
The following will show the verification methods of these two ways.
Way 1: Use CUE to verify
Generate the CUE file to verify the builder info.
// The predicate must match the following constraints.
predicate: {
builder: {
id: "https://alauda.io/builders/tekton/v1"
}
}
Save the CUE file to builder.cue
Verify the builder info with cosign.
# Disable tlog upload and enable private infrastructure
$ export COSIGN_TLOG_UPLOAD=false
$ export COSIGN_PRIVATE_INFRASTRUCTURE=true
$ export IMAGE=<<registry>/test/chains/demo-1:latest@sha256:93635f39cb31de5c6988cdf1f10435c41b3fb85570c930d51d41bbadc1a90046>
$ cosign verify-attestation --key cosign.pub --type slsaprovenance --policy builder.cue $IMAGE
Receive the output like this, means the builder info verification is successful.
will be validating against CUE policies: [builder.cue]
will be validating against CUE policies: [builder.cue]
Verification for <registry>/test/chains/demo-1:latest@sha256:8ac1af8dd89652bf32abbbd0c5f667ae9fe6d92c91972617e70b5398303c8e27 --
The following checks were performed on each of these signatures:
- The cosign claims were validated
- The signatures were verified against the specified public key
{"payloadType":"application/vnd.in-toto+json","payload":"","signatures":[]}
Change the builder id in the builder.cue
file to an other value https://alauda.io/builders/tekton/v2
, and verify again.
$ cosign verify-attestation --key cosign.pub --type slsaprovenance --policy builder.cue $IMAGE
Receive the output like this, means the builder info verification is failed.
will be validating against CUE policies: [builder.cue]
will be validating against CUE policies: [builder.cue]
There are 2 number of errors occurred during the validation:
- predicate.builder.id: conflicting values "https://alauda.io/builders/tekton/v1" and "https://alauda.io/builders/tekton/v2"
- predicate.builder.id: conflicting values "https://alauda.io/builders/tekton/v1" and "https://alauda.io/builders/tekton/v2"
Error: 2 validation errors occurred
error during command execution: 2 validation errors occurred
Way 2: Use Rego to verify
Generate the Rego file to verify the builder info.
package signature
default allow = false
# Define the allowed builder.id
allowed_builder_id = "https://alauda.io/builders/tekton/v1"
# Verify the builder.id
allow {
# Check if the builder.id in the predicate is equal to the allowed value
input.predicate.builder.id == allowed_builder_id
}
# Return error message when not match
deny[msg] {
input.predicate.builder.id != allowed_builder_id
msg := sprintf("unexpected builder.id: %v, expected: %v", [input.predicate.builder.id, allowed_builder_id])
}
Save the Rego file to builder.rego
Verify the builder info with cosign.
# Disable tlog upload and enable private infrastructure
$ export COSIGN_TLOG_UPLOAD=false
$ export COSIGN_PRIVATE_INFRASTRUCTURE=true
$ export IMAGE=<<registry>/test/chains/demo-1:latest@sha256:93635f39cb31de5c6988cdf1f10435c41b3fb85570c930d51d41bbadc1a90046>
$ cosign verify-attestation --key cosign.pub --type slsaprovenance --policy builder.rego $IMAGE
Receive the output like this, means the builder info verification is successful.
will be validating against Rego policies: [builder.rego]
will be validating against Rego policies: [builder.rego]
Verification for <registry>/test/chains/demo-1:latest --
The following checks were performed on each of these signatures:
- The cosign claims were validated
- The signatures were verified against the specified public key
{"payloadType":"application/vnd.in-toto+json","payload":"","signatures":[]}
Change the builder id in the builder.rego
file to an other value https://alauda.io/builders/tekton/v2
, and verify again.
$ cosign verify-attestation --key cosign.pub --type slsaprovenance --policy builder.rego $IMAGE
Receive the output like this, means the builder info verification is failed.
will be validating against Rego policies: [builder.rego]
will be validating against Rego policies: [builder.rego]
There are 2 number of errors occurred during the validation:
- expression value, false, is not true
- expression value, false, is not true
Error: 2 validation errors occurred
error during command execution: 2 validation errors occurred
Step 9: Verify the signature with Kyverno
TIP
This step requires cluster administrator privileges.
The content of the provenance is roughly as follows, we will use the builder.id
field to verify the build environment.
{
"_type": "https://in-toto.io/Statement/v0.1",
"predicateType": "https://slsa.dev/provenance/v0.2",
"predicate": {
"buildType": "tekton.dev/v1beta1/TaskRun",
"builder": {
"id": "https://alauda.io/builders/tekton/v1"
},
"materials": [
{
"digest": {
"sha256": "8d5ea9ecd9b531e798fecd87ca3b64ee1c95e4f2621d09e893c58ed593bfd4c4"
},
"uri": "oci://<registry>/devops/tektoncd/hub/buildah"
}
],
"metadata": {
"buildFinishedOn": "2025-06-06T10:21:27Z",
"buildStartedOn": "2025-06-06T10:20:55Z"
}
}
}
Step 9.1: Create a Kyverno policy to allow only images built in specific build environments to be deployed
The policy is as follows:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-tekton-built-images
spec:
webhookConfiguration:
failurePolicy: Fail
timeoutSeconds: 30
background: false
rules:
- name: check-image
match:
any:
- resources:
kinds:
- Pod
namespaces:
- policy
verifyImages:
- imageReferences:
- "*"
# - "<registry>/test/*"
skipImageReferences:
- "ghcr.io/trusted/*"
failureAction: Enforce
verifyDigest: false
required: false
useCache: false
imageRegistryCredentials:
allowInsecureRegistry: true
secrets:
# The credential needs to exist in the namespace where kyverno is deployed
- registry-credentials
attestations:
- type: https://slsa.dev/provenance/v0.2
attestors:
- entries:
- keys:
publicKeys: |- # <- The public key of the signer
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFZNGfYwn7+b4uSdEYLKjxWi3xtP3
UkR8hQvGrG25r0Ikoq0hI3/tr0m7ecvfM75TKh5jGAlLKSZUJpmCGaTToQ==
-----END PUBLIC KEY-----
ctlog:
ignoreSCT: true
rekor:
ignoreTlog: true
conditions:
- all:
- key: "{{ builder.id }}"
operator: Equals
value: "https://alauda.io/builders/tekton/v1"
message: "The builder.id must be equal to https://alauda.io/builders/tekton/v1, not {{ builder.id }}"
Explanation of YAML fields
- The policy is largely consistent with the one in Image Signature Verification. Below only introduces the differences.
spec.rules[0].verifyImages[].attestations[0].conditions
type
: The slsa provenance type is https://slsa.dev/provenance/v0.2
or https://slsa.dev/provenance/v1
.
attestors
: the same as above.
conditions
: The conditions to be verified.
all
: All conditions must be met.
key: "{{ builder.id }}"
: This check the builder.id
field in the attestation is equal to https://alauda.io/builders/tekton/v1
Need to adjust the configuration
spec.rules[].attestors[].entries[].keys.publicKeys
: The public key of the signer.
- This public key is the same as the public key
cosign.pub
in the signing-secrets
secret.
- The public key can be obtained from the Get the Signing Public Key section.
Save the policy to a yaml file named kyverno.verify-tekton-built-images.yaml
and apply it with:
$ kubectl apply -f kyverno.verify-tekton-built-images.yaml
clusterpolicy.kyverno.io/verify-tekton-built-images configured
Step 9.2: Verify the policy
In the policy
namespace where the policy is defined, create a Pod to verify the policy.
Use the built image to create a Pod.
$ export NAMESPACE=<policy>
$ export IMAGE=<<registry>/test/chains/demo-1:latest@sha256:93635f39cb31de5c6988cdf1f10435c41b3fb85570c930d51d41bbadc1a90046>
$ kubectl run -n $NAMESPACE built --image=${IMAGE} -- sleep 3600
pod/built created
The Pod will be created successfully.
$ kubectl get pod -n $NAMESPACE built
NAME READY STATUS RESTARTS AGE
built 1/1 Running 0 10s
Change the builder id in the ClusterPolicy
to an other value https://alauda.io/builders/tekton/v2
, and verify again.
conditions:
- all:
- key: "{{ builder.id }}"
operator: Equals
value: "https://alauda.io/builders/tekton/v2"
message: "The builder.id must be equal to https://alauda.io/builders/tekton/v2, not {{ builder.id }}"
$ kubectl run -n $NAMESPACE unbuilt --image=${IMAGE} -- sleep 3600
Receive the output like this, means the Pod is blocked by the policy.
Error from server: admission webhook "mutate.kyverno.svc-fail" denied the request:
resource Pod/policy/unbuilt was blocked due to the following policies
verify-tekton-built-images:
check-image: 'image attestations verification failed, verifiedCount: 0, requiredCount:
1, error: .attestations[0].attestors[0].entries[0].keys: attestation checks failed
for <registry>/test/chains/demo-1@sha256:93635f39cb31de5c6988cdf1f10435c41b3fb85570c930d51d41bbadc1a90046
and predicate https://slsa.dev/provenance/v0.2: The builder.id must be equal to
https://alauda.io/builders/tekton/v2, not https://alauda.io/builders/tekton/v1'
Step 10: Clean up the resources
Delete the Pods created in the previous steps.
$ export NAMESPACE=<policy>
$ kubectl delete pod -n $NAMESPACE built
Delete the policy.
$ kubectl delete clusterpolicy verify-tekton-built-images
Expected Results
After completing this guide:
- You have a working setup with Tekton Chains for generating SLSA provenance
- Your container images are automatically signed with build system provenance during the build process
- You can verify the build environment of images using either CUE or Rego policies
- Only images built in the specified build environment can be deployed in the specified namespace
- Images built in unauthorized build environments are automatically blocked by Kyverno policies
- You have implemented a basic build system provenance verification control for your container images
This guide provides a foundation for implementing build system provenance verification in your CI/CD pipelines. In a production environment, you should:
- Configure proper namespace isolation and access controls
- Implement secure key management for signing keys
- Set up monitoring and alerting for policy violations
- Regularly rotate signing keys and update security policies
- Consider implementing additional security controls like vulnerability scanning
References