Checkpoint Lab — Software Supply-Chain Security, SBOMs, Signatures, Provenance, Trusted Agents, and Dependency Controls
Build one synthetic artifact on a trusted disposable agent, generate its SBOM and provenance evidence, sign it, tamper with a copy, and prove the policy accepts only the intended bytes.
Learning objectives
- Predict and then verify build/agent/artifact/evidence state changes.
- Produce an evidence packet tied to one source SHA and one Jenkins build.
- Demonstrate successful signature verification for the original artifact.
- Demonstrate failed verification for a tampered copy without destroying first-failure evidence.
- Write a limitations note describing exactly what the evidence can and cannot claim.
1. Scenario and predictions
You maintain labs/supply-chain-checkpoint. It runs only
on supply-chain-lab, a disposable agent with no
production network/credentials. Before running, write down two
predictions:
- The build will create exactly one artifact whose SHA-256 becomes the subject identity for all downstream evidence.
- The original artifact will verify with the expected lab public key, while a one-line modification to a copy will fail verification.
Add a third prediction if using provenance: its subject digest must exactly equal the recorded artifact digest.
2. Setup and preflight
- Confirm Jenkins LTS/Java and security advisory status.
- Confirm the built-in node is not used for the lab.
- Confirm the agent label maps only to the disposable lab agent.
-
Confirm
syft versionreports 1.51.1 andcosign versionreports 3.1.3, or record any deliberate newer tested versions. -
Create a Secret Text credential
lab-cosign-passphrasecontaining a fake disposable passphrase. Scope it to the lab folder/job where possible. - Confirm the repository contains only synthetic code/data.
set -euo pipefail
printf 'source='; git rev-parse HEAD
printf 'branch='; git branch --show-current || true
printf 'syft='; syft version
printf 'cosign='; cosign version
printf 'host='; hostname
printf 'platform='; uname -a
3. Checkpoint Pipeline
This Jenkinsfile is intentionally compact because Lessons 1–4 already explained every layer. It still preserves the evidence chain and cleanup guardrails.
pipeline {
agent { label 'supply-chain-lab' }
options { timestamps(); disableConcurrentBuilds(); buildDiscarder(logRotator(numToKeepStr: '10')) }
environment {
ARTIFACT = "dist/checkpoint-${BUILD_NUMBER}.txt"
EVIDENCE = 'evidence'
}
stages {
stage('Preflight + identity') {
steps {
sh '''set -euo pipefail
test "$NODE_NAME" != 'built-in'
mkdir -p dist "$EVIDENCE"
git rev-parse HEAD | tee "$EVIDENCE/source-sha.txt"
printf '%s\n' "$JOB_NAME" > "$EVIDENCE/job-name.txt"
printf '%s\n' "$BUILD_NUMBER" > "$EVIDENCE/build-number.txt"
printf '%s\n' "$NODE_NAME" > "$EVIDENCE/node-name.txt"
syft version > "$EVIDENCE/syft-version.txt"
cosign version > "$EVIDENCE/cosign-version.txt"
'''
}
}
stage('Build + digest') {
steps {
sh '''set -euo pipefail
printf 'checkpoint source=%s build=%s\n' "$(cat "$EVIDENCE/source-sha.txt")" "$BUILD_NUMBER" > "$ARTIFACT"
sha256sum "$ARTIFACT" | tee "$EVIDENCE/artifact.sha256"
'''
}
}
stage('SBOM') {
steps {
sh '''set -euo pipefail
syft dir:. -o cyclonedx-json="$EVIDENCE/sbom.cdx.json"
sha256sum "$EVIDENCE/sbom.cdx.json" > "$EVIDENCE/sbom.cdx.json.sha256"
'''
}
}
stage('Provenance teaching statement') {
steps {
sh '''set -euo pipefail
D="$(awk '{print $1}' "$EVIDENCE/artifact.sha256")"
S="$(cat "$EVIDENCE/source-sha.txt")"
jq -n --arg n "$(basename "$ARTIFACT")" --arg d "$D" --arg s "$S" --arg i "$BUILD_TAG" \
'{"_type":"https://in-toto.io/Statement/v1","subject":[{"name":$n,"digest":{"sha256":$d}}],"predicateType":"https://slsa.dev/provenance/v1","predicate":{"buildDefinition":{"buildType":"https://devops-academy.example/jenkins-checkpoint/v1","externalParameters":{"sourceSha":$s},"internalParameters":{},"resolvedDependencies":[]},"runDetails":{"builder":{"id":"https://devops-academy.example/builder/supply-chain-lab"},"metadata":{"invocationId":$i}}}}' \
> "$EVIDENCE/provenance.json"
test "$(jq -r '.subject[0].digest.sha256' "$EVIDENCE/provenance.json")" = "$D"
'''
}
}
stage('Sign + verify') {
steps {
withCredentials([string(credentialsId: 'lab-cosign-passphrase', variable: 'COSIGN_PASSWORD')]) {
sh '''set -euo pipefail
umask 077
cosign generate-key-pair
cosign sign-blob --yes --key cosign.key --bundle "$EVIDENCE/artifact.sigstore.json" "$ARTIFACT"
cosign verify-blob --key cosign.pub --bundle "$EVIDENCE/artifact.sigstore.json" "$ARTIFACT" \
| tee "$EVIDENCE/original-verification.txt"
rm -f cosign.key
'''
}
}
}
stage('Tamper rejection') {
steps {
sh '''set -euo pipefail
cp "$ARTIFACT" "$EVIDENCE/tampered.txt"
printf 'tampered\n' >> "$EVIDENCE/tampered.txt"
sha256sum "$ARTIFACT" "$EVIDENCE/tampered.txt" > "$EVIDENCE/tamper-digests.txt"
set +e
cosign verify-blob --key cosign.pub --bundle "$EVIDENCE/artifact.sigstore.json" "$EVIDENCE/tampered.txt" \
> "$EVIDENCE/tampered-verification.txt" 2>&1
rc=$?
set -e
printf 'exit=%s\n' "$rc" >> "$EVIDENCE/tampered-verification.txt"
test "$rc" -ne 0
'''
}
}
}
post {
always {
archiveArtifacts artifacts: 'dist/**,evidence/**,cosign.pub', fingerprint: true, allowEmptyArchive: true
}
}
}
4. Required evidence packet
| Evidence | Minimum field/value to capture |
|---|---|
| Controller baseline | Jenkins core/LTS, Java, relevant plugin catalog/advisory check date |
| Build identity | Job full name, build number/URL/cause, source SHA, Jenkinsfile/library refs |
| Agent identity | Node/label/executor/workspace, OS/architecture, agent JVM/Remoting where available |
| Toolchain | Syft/Cosign versions and installation verification source/checksum process |
| Dependency state | Lockfiles/checksums or explicit note that the synthetic artifact has no external dependencies |
| Artifact | Path, size, SHA-256 |
| SBOM | Format/version, generator/version, SBOM digest |
| Provenance | Predicate type, subject digest, builder ID, source parameter, invocation/build ID |
| Signature | Bundle, expected lab public key/fingerprint, successful verification transcript |
| Negative test | Tampered digest and non-zero verification transcript |
| Limitations | Claims the lab does not establish |
5. Verification checklist
- The build ran on the intended disposable agent, not the built-in node.
-
artifact.sha256matches a fresh SHA-256 of the archived original artifact. - The provenance subject SHA-256 equals the artifact SHA-256 exactly.
- The SBOM parses as CycloneDX JSON and has its own retained checksum.
- The Cosign bundle verifies the original artifact using the expected lab public key.
- The tampered copy has a different digest and verification fails non-zero.
-
No
cosign.key, passphrase or real credential appears in archived files or console output. - The evidence packet records assumptions and the advisory/tool check date.
6. Write the “can / cannot claim” note
| Evidence supports | Evidence does not support |
|---|---|
| These exact bytes matched the recorded digest at verification time. | The software is vulnerability-free or defect-free. |
| The lab key signed the original artifact and the verifier checked that key. | A production organizational identity approved it. |
| The SBOM generator observed the components represented in this document. | The SBOM is perfectly complete for every ecosystem. |
| The provenance statement names the source SHA, builder ID and subject digest. | The builder meets a particular SLSA level merely because the JSON follows the predicate shape. |
| The tampered copy is not the signed original. | The original artifact is safe to deploy to every environment. |
7. Cleanup and rollback
Cleanup only the resources created by this checkpoint.
set -euo pipefail
# Run only inside the disposable workspace after archiving evidence.
rm -f cosign.key
rm -rf dist evidence cosign.pub
# Do not delete Jenkins jobs/credentials/agents by ambiguous "latest" logic.
If you created the Jenkins credential or agent solely for this lab, remove them by exact ID/name after confirming no other lab depends on them. Preserve the archived evidence packet for course review.
8. What this chapter adds to a production operating model
You now have a policy-shaped evidence chain rather than a collection of security tools: immutable source and artifact identity, explicit builder trust, dependency state, SBOM, provenance, signing identity and a negative tamper test. A production platform strengthens each boundary with reviewed immutable agent images, workload identity/KMS or keyless signing, protected branch/library trust, repository admission policy and independent verification at promotion.
Chapter 32 continues from here by moving the same verified bytes into artifact repositories and promotion flows without rebuilding them.
Knowledge check
Answer before revealing the explanation.
1. The original artifact verifies, but the provenance subject digest differs. Is the checkpoint successful?
No. The evidence chain is internally inconsistent. Preserve both files and diagnose why provenance selected the wrong artifact/digest.
2. The tampered copy also verifies successfully. What is your first action?
Stop promotion, preserve the bundle, keys, both digests, command/version output and logs. Treat it as a verification-policy/tooling failure until proven otherwise; do not regenerate signatures.
3. Why must the private key be absent from the archived evidence packet?
The packet is for verification and audit. The private key is signing authority; archiving it would let readers forge new valid signatures.
4. What is the correct bridge to Chapter 32?
Publish or promote the exact artifact bytes identified by the verified digest along with their evidence; do not rebuild from source during promotion.
Official references and version notes
Use primary documentation because supply-chain formats, signing defaults and Jenkins security guidance evolve.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.