5.1 Sign (keyless)

On this page you prove that the application image you built carries a keyless signature from Red Hat Trusted Artifact Signer, and that cosign verify accepts it.

Track 5 is sign and attest. This is the first of its three pages. You sign the application digest from 4.4 Build and SBOM. The Hummingbird base already has Red Hat’s key, from Track 1. Lightwell already signed the libraries. This signature is the application’s. The seeded pipeline includes Task cosign-sign-keyless. The PipelineRun still has placeholders until you fill the in-cluster Fulcio, Rekor, and TUF URLs. 5.3 Disconnected verify adds a second, key-based signature on the same digest. Keyless alone is not the end of Track 5.

Why this control exists

Integrity of the application image is a signature on the digest you built. The pipeline service account presents an OIDC token. Fulcio issues a short-lived certificate. Rekor records the signature. TUF distributes the trust root. cosign verify accepts that identity: the pipeline service account in the build namespace, issued by https://kubernetes.default.svc.

The URLs inside the build namespace are the in-cluster Services, fulcio_internal_url and its siblings on ConfigMap demo-userinfo-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 Premier sign dependencies. They do not sign this application digest. Premier does not replace Trusted Artifact Signer.

Keyless RHTAS Fulcio Rekor TUF versus later key-based verify

The figure is the two signatures on one digest. This page is the left column: the pipeline service account, Fulcio, Rekor, and TUF. The key-based path, on 5.3, is a key pair under ~/lab-trust, cosign sign --key, and ConfigMap lab-cosign-pubkey. Mirrored TUF belongs to the appendix exercise for a keyless verifier. The cluster stays on the network for both signatures.

cosign is already on the Showroom PATH. Leave github.com out of the lab. Do not curl it for the CLI.

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:

  • Fill the PipelineRun with the in-cluster Fulcio, Rekor, and TUF URLs and get cosign-sign-keyless to Succeeded

  • Run cosign verify on the application digest with the pipeline service-account identity

  • Record that digest for Track 6

  • Leave the key-based cosign sign --key for 5.3

The signer URLs

Success is cosign on PATH, then the signing model and both URL families: internal Services for the pipeline, and Route hints for the Showroom verify. Confirm the hints against the live Routes. A PipelineRun that still contains apps.<domain> fails the sign task.

Read the RHTAS URLs
command -v cosign
oc -n trusted-artifact-signer get configmap demo-userinfo-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

The block below is cosign verify against a different issuer and a different image. Using it as the scored verify fails the Check.

# Worked example ONLY — fake issuer/subject. Do not use as the scored verify.
cosign verify \
  --certificate-identity-regexp='https://example.invalid/not-the-pipeline-sa' \
  --certificate-oidc-issuer='https://example.invalid/oidc' \
  image-registry.openshift-image-registry.svc:5000/example/not-your-app:latest

Your change

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.

Confirm Gitea has the filled PipelineRun
cd /tmp/spring-boot-lw-poc
git fetch
git show origin/main:.tekton/pipelinerun.yaml | grep -E 'STUDENT_REPO_URL_PLACEHOLDER|<lab-namespace>' \
  && echo 'STILL HAS PLACEHOLDERS — commit the filled file from 4.4' \
  || echo 'Gitea pipelinerun is filled'

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.

Create a run when 4.4 did not already sign
BUILD_NS="$(oc -n gitea get configmap demo-userinfo-gitea -o jsonpath='{.data.student_build_namespace}')"
cd /tmp/spring-boot-lw-poc
oc -n "${BUILD_NS}" apply -f .tekton/pipeline.yaml
oc -n "${BUILD_NS}" create -f .tekton/pipelinerun.yaml
oc -n "${BUILD_NS}" get pipelinerun -w
PipelineRun graph with cosign-sign-keyless Succeeded

The capture is the pipeline graph once cosign-sign-keyless has succeeded. Tagging the image into staging is Track 6. Generating a key pair, or running cosign sign --key, is 5.3.

Check

Verify the application tag spring-boot-lw-poc:latest in the build namespace. 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.

Success is exit code 0, and a verification block for your application digest.

Verify the application digest
BUILD_NS="$(oc -n gitea get configmap demo-userinfo-gitea -o jsonpath='{.data.student_build_namespace}')"
TUF="$(oc -n trusted-artifact-signer get configmap demo-userinfo-rhtas -o jsonpath='{.data.tuf_url_hint}')"
REKOR="$(oc -n trusted-artifact-signer get configmap demo-userinfo-rhtas -o jsonpath='{.data.rekor_url_hint}')"
IMAGE="image-registry.openshift-image-registry.svc:5000/${BUILD_NS}/spring-boot-lw-poc:latest"
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}"

Record the digest. Track 6 promotes it.

BUILD_NS="$(oc -n gitea get configmap demo-userinfo-gitea -o jsonpath='{.data.student_build_namespace}')"
oc -n "${BUILD_NS}" get istag spring-boot-lw-poc:latest -o jsonpath='{.image.dockerImageReference}{"\n"}'
Fill the report

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.

Run the Validate Job
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 page still opens if you skip this one. Lightwell signed the library. This page signed the image.

Official documentation

Key takeaways

  • Keyless signing is the connected path for the application digest.

  • The pipeline uses in-cluster Fulcio, Rekor, and TUF. Showroom verify uses the Route hints.

  • 5.3 adds a key-based signature on the same digest. Track 6 admission needs that public key.