5.1 Verify the application’s keyless signature
Track 4 produced an application image and an SBOM. In this module, confirm that the pipeline signed that application image with Red Hat Trusted Artifact Signer (RHTAS), then verify the signature against the pipeline service-account identity. Continue in the Showroom terminal and your /home/lab-user/lightwell-workspace/spring-boot-lw-poc clone.
Track 5 has three outputs: keyless verification evidence in 5.1, a learner-owned signature policy in 5.2, and a second signature plus a public key for Track 6 admission in 5.3. Keep Track 4’s run name, image reference, and SBOM available so you can recognize when a new build replaces them.
Why this control exists
A signature connects the exact application image you built to the identity that signed it. Here, that identity is the pipeline service account in your build namespace. During signing, cosign creates a temporary key pair and presents the service account’s OpenID Connect (OIDC) token to Fulcio, the certificate authority. Fulcio checks the token and issues a short-lived certificate that binds the public key to that service-account identity. cosign then signs the image digest with the temporary private key, and Rekor, the transparency log, records the signing evidence.
When you run cosign verify, it checks the signature against the image digest, validates the certificate and transparency-log evidence, and requires the signer identity and token issuer you specify. In this lab, the expected signer is your build namespace’s pipeline service account, and the expected issuer is https://kubernetes.default.svc. The Update Framework (TUF) supplies the trusted certificates and public keys used to validate Fulcio and Rekor. Successful verification establishes that the image matches the signed digest and that the signature belongs to the expected pipeline identity.
The top row follows signing inside the pipeline; the bottom row follows verification from Showroom. TUF supplies verification trust material rather than signing the application. In module 5.3 you add a separate key-based signature for Track 6 admission.
The URLs inside the build namespace are the in-cluster Services, fulcio_internal_url and its siblings on ConfigMap lab-rhtas. After 4.3 the build namespace cannot reach the HTTPS Routes. The Route hints (fulcio_url_hint and its siblings) are for the Showroom cosign verify only. A verify that uses the Hummingbird key, or a fake issuer, fails the Check. Admission in Track 6 still needs the public key from 5.3, because native ImagePolicy cannot store this service-account URI. Keep both signatures.
Lightwell Network and Lightwell Clearinghouse sign dependencies. They do not sign this application digest. Clearinghouse does not replace Trusted Artifact Signer.
|
|
In a customer engagement, CI produces a signed image through Sigstore or Trusted Artifact Signer. You prove that your BuildConfig digest verifies against this cluster’s Trusted Artifact Signer. Admission is Track 6.
Learning objectives
By the end of this module, you will be able to:
-
Distinguish the pipeline’s internal signing endpoints from Showroom’s verification Routes.
-
Confirm the committed run configuration and identify a successful signing TaskRun.
-
Verify an immutable application reference against the pipeline identity and issuer.
-
Save the verified digest and explain which signature belongs to the application.
The signer URLs
The commands should locate cosign on PATH and print the signing model and both URL families: internal Services for the pipeline, and Route hints for verification from Showroom. Confirm the hints against the live Routes. A PipelineRun that still contains apps.<domain> fails the sign task.
command -v cosign
oc -n trusted-artifact-signer get configmap lab-rhtas \
-o jsonpath='{.data.signing_model}{"\n"}{.data.fulcio_internal_url}{"\n"}{.data.rekor_internal_url}{"\n"}{.data.tuf_internal_url}{"\n"}{.data.fulcio_url_hint}{"\n"}{.data.rekor_url_hint}{"\n"}{.data.tuf_url_hint}{"\n"}'
oc -n trusted-artifact-signer get routes
Exercise
Confirm the committed signing configuration
If 4.4 already ran cosign-sign-keyless to Succeeded, you still confirm that Gitea has the filled PipelineRun. Check 12 reads .tekton/pipelinerun.yaml from Gitea, not the live object. STUDENT_REPO_URL_PLACEHOLDER or <lab-namespace> still on main fails the Check even when the sign task succeeded. Success of the grep is the line Gitea pipelinerun is filled. If you see STILL HAS PLACEHOLDERS, commit the filled file from 4.4 and push it.
cd /home/lab-user/lightwell-workspace/spring-boot-lw-poc
git fetch origin || { echo "Could not fetch Gitea"; exit 1; }
git show origin/main:.tekton/pipelinerun.yaml > /tmp/track5-pipelinerun.yaml \
|| { echo "Could not read committed PipelineRun"; exit 1; }
grep -E 'STUDENT_REPO_URL_PLACEHOLDER|<lab-namespace>|apps\.<domain>' /tmp/track5-pipelinerun.yaml \
&& echo 'STILL HAS PLACEHOLDERS — commit the filled file from 4.4' \
|| echo 'Gitea pipelinerun is filled'
Inspect the signing TaskRun
Use the run name saved in Track 4 as RUN. Inspect its TaskRuns with the command below. A True condition for cosign-sign-keyless is the required result; an older successful run is not evidence for a newly built image.
BUILD_NS="$(oc -n gitea get configmap lab-gitea -o jsonpath='{.data.student_build_namespace}')"
printf 'Track 4 PipelineRun: '
read -r RUN
oc -n "$BUILD_NS" get taskrun -l "tekton.dev/pipelineRun=$RUN" \
-o custom-columns='NAME:.metadata.name,TASK:.metadata.labels.tekton\.dev/pipelineTask,STATUS:.status.conditions[0].status,REASON:.status.conditions[0].reason'
If the sign task has not succeeded yet, edit .tekton/pipelinerun.yaml on lw-student/spring-boot-lw-poc. Replace Fulcio, Rekor, and TUF with fulcio_internal_url, rekor_internal_url, and tuf_internal_url. Replace STUDENT_REPO_URL_PLACEHOLDER with student_repo_internal_url. Replace <lab-namespace> with lw-poc-build. Commit and push that file, the same way 4.4 did, then create a new run. Success is cosign-sign-keyless Succeeded. ACS may still fail. This Check is the signature.
BUILD_NS="$(oc -n gitea get configmap lab-gitea -o jsonpath='{.data.student_build_namespace}')"
cd /home/lab-user/lightwell-workspace/spring-boot-lw-poc
oc -n "${BUILD_NS}" apply -f .tekton/pipeline.yaml
RUN="$(oc -n "${BUILD_NS}" create -f .tekton/pipelinerun.yaml -o jsonpath='{.metadata.name}')"
oc -n "${BUILD_NS}" get pipelinerun "$RUN" -w
The diagram shows which evidence belongs together. After a rerun, press Ctrl-C to stop watching, then repeat the TaskRun inspection for the new RUN. Save fresh Track 4 image and SBOM evidence if that run rebuilt the image. Tagging the image into staging is Track 6. Generating a key pair, or running cosign sign --key, is 5.3.
Verify and save the application digest
Resolve the application tag spring-boot-lw-poc:latest in the build namespace to its immutable @sha256: reference before verification. Avoid concurrent builds while collecting evidence. The identity is the pipeline service account in that namespace. The issuer is the Kubernetes API. Showroom uses the Route hints for TUF and Rekor, because this shell is outside the build NetworkPolicy. HOME is /tmp/lab-home so cosign login does not write a Docker config into the real home directory.
If verify fails, read the SAN from the Rekor entry or from the cosign verify error. The identity regexp below is the one this cluster’s pipeline service account presents.
The command should return exit code 0 and print a verification block for your application digest.
BUILD_NS="$(oc -n gitea get configmap lab-gitea -o jsonpath='{.data.student_build_namespace}')"
TUF="$(oc -n trusted-artifact-signer get configmap lab-rhtas -o jsonpath='{.data.tuf_url_hint}')"
REKOR="$(oc -n trusted-artifact-signer get configmap lab-rhtas -o jsonpath='{.data.rekor_url_hint}')"
IMAGE="$(oc -n "$BUILD_NS" get istag spring-boot-lw-poc:latest -o jsonpath='{.image.dockerImageReference}')"
[[ "$IMAGE" == *@sha256:* ]] || { echo "No immutable application reference"; exit 1; }
mkdir -p /tmp/lab-home
export HOME=/tmp/lab-home
cosign initialize --mirror="${TUF}" --root="${TUF}/root.json"
cosign login -u unused -p "$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
image-registry.openshift-image-registry.svc:5000
cosign verify \
--rekor-url="${REKOR}" \
--certificate-identity-regexp="^https://kubernetes.io/namespaces/${BUILD_NS}/serviceaccounts/pipeline$" \
--certificate-oidc-issuer="https://kubernetes.default.svc" \
--allow-http-registry --allow-insecure-registry \
"${IMAGE}" > /tmp/track5-keyless-verification.json
VERIFY_STATUS=$?
cat /tmp/track5-keyless-verification.json
test "$VERIFY_STATUS" -eq 0 || { echo "Signature verification failed"; exit 1; }
Save the reference only after verification succeeds. Track 6 promotes the final verified digest.
mkdir -p /home/lab-user/lightwell-signing-evidence
printf '%s\n' "$IMAGE" > /home/lab-user/lightwell-signing-evidence/image.txt
printf '%s\n' "$RUN" > /home/lab-user/lightwell-signing-evidence/pipelinerun-name.txt
cp /tmp/track5-keyless-verification.json /home/lab-user/lightwell-signing-evidence/
export HOME=/home/lab-user
Review
Compare the successful signing TaskRun with the verification output and saved image.txt. The signature must identify this namespace’s pipeline service account and the Kubernetes issuer. A base-image signature or a library signature does not establish who signed your application. Be ready to explain why the pipeline uses internal Services while Showroom uses Route hints.
Unlike Tracks 1–4, Track 5 also retains the workshop report and Validate Job below. Run them after reviewing your own evidence; Job success is not a replacement for understanding the verification output.
The Job also grades ConfigMap report-12-sign-keyless key what_you_signed. Set it to app-digest. Empty or REPLACE_ME fails even if cluster state is correct. Do not copy validate-docs.
oc -n lw-poc-validate edit configmap report-12-sign-keyless
The scored Check is Job validate-12-sign-keyless in lw-poc-validate. Direct nav URLs still work (honor system). Unlimited retries. There is no Solve.
oc -n lw-poc-validate delete job validate-12-sign-keyless --ignore-not-found
TPL='job-12.yaml'
oc -n lw-poc-validate get configmap validate-job-templates \
-o go-template="{{ index .data \"${TPL}\" }}" | oc create -f -
oc -n lw-poc-validate logs -f job/validate-12-sign-keyless
Expect CHECK PASSED: before you continue. CHECK FAILED: names the object to fix. Delete the Job and create it again as often as you need.
The Validate Job grades the keyless signature on the application digest. The next module still opens if you skip this one. Lightwell signed the library. This module signed the image.
Carry the result into module 5.2
Keep /home/lab-user/lightwell-signing-evidence. Next you will configure Conforma to reject an unsigned builder and accept the application signature. A new PipelineRun can produce a different digest; refresh verification and SBOM evidence when it does.
Official documentation
-
Trusted Artifact Signer deployment guide (Fulcio, Rekor, TUF,
cosign, and Conforma)