5.3 Disconnected verify
Overview
5.1 already signed the app digest keylessly with RHTAS. This lab adds a second signature on that same digest. You generate a cosign key pair, run cosign sign --key, then cosign verify --key. Do not treat keyless-only as Track 5 done.
The cluster stays networked. You practice the procedure for when Fulcio is unreachable. You are not building a physical air-gap. Mirrored TUF for keyless verifiers, and a simulated media drop, live in Appendix: disconnected transfer.
Why it matters
You must still verify when Fulcio is unreachable. A key-based signature you control travels with the digest. Native ImagePolicy in 6.1 cannot hold the 5.1 service-account URI. CRI-O admission uses this cosign.pub.
You generate the pair, sign with --tlog-upload=false, and publish the PEM to ConfigMap lab-cosign-pubkey so 6.1 can render ImagePolicy. Do not claim this cluster is a diode.
Lightwell Network and Clearinghouse Premier artifacts may already have their own signatures. This lab signs the app image for admission. Premier does not replace the key pair.
What does it solve
-
No way to verify when public key servers are out of reach
-
Treating keyless-only as Track 5 done
-
Committing private keys
Why a second signature
Keyless (5.1) needs Fulcio at sign time and a TUF root at verify time. When Fulcio is unreachable you still need a public key you control. You add that signature here. Native ImagePolicy signedEmail cannot hold the 5.1 Kubernetes service-account URI, so 6.1 CRI-O admission uses this cosign.pub. Keep the keyless signature on the image. Do not strip it.
The two-column picture is on 5.1 Keyless vs keys.
Discover the 5.1 digest
cosign is installed by Welcome Environment setup into ~/.local/bin. Private keys never go in Git or a screenshot.
BUILD_NS="$(oc -n gitea get configmap demo-userinfo-gitea -o jsonpath='{.data.student_build_namespace}')"
IMAGE="image-registry.openshift-image-registry.svc:5000/${BUILD_NS}/spring-boot-lw-poc:latest"
oc -n "${BUILD_NS}" get istag spring-boot-lw-poc:latest \
-o jsonpath='{.image.dockerImageReference}{"\n"}'
ls ~/lab-trust 2>/dev/null || echo 'lab-trust empty (expected before your change)'
Worked example (not the Check)
# Worked example ONLY — different directory and key name. Do not use as the Check.
mkdir -p ~/example-not-lab-trust
# COSIGN_PASSWORD="" cosign generate-key-pair (would write demo.pub here)
cosign verify --key ~/example-not-lab-trust/demo.pub \
--insecure-ignore-tlog \
image-registry.openshift-image-registry.svc:5000/example/not-your-app:latest
Your change: key pair, sign --key
Empty password is for this lab terminal only so the execute block is non-interactive. Do not reuse that empty-password pattern when you issue keys for your own organization. Do not commit cosign.key. export HOME=/tmp/lab-home is only so cosign login does not write Docker config into the real home. Keep --key / --from-file on /home/lab-user/lab-trust (tilde follows HOME).
BUILD_NS="$(oc -n gitea get configmap demo-userinfo-gitea -o jsonpath='{.data.student_build_namespace}')"
IMAGE="image-registry.openshift-image-registry.svc:5000/${BUILD_NS}/spring-boot-lw-poc:latest"
mkdir -p ~/lab-trust
cd ~/lab-trust
if [[ ! -f cosign.key ]]; then
COSIGN_PASSWORD="" cosign generate-key-pair
fi
export HOME=/tmp/lab-home
mkdir -p "${HOME}"
cosign login -u unused -p "$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
image-registry.openshift-image-registry.svc:5000
COSIGN_PASSWORD="" cosign sign --key /home/lab-user/lab-trust/cosign.key \
--tlog-upload=false \
--allow-http-registry --allow-insecure-registry \
"${IMAGE}"
# ImagePolicy signedEmail cannot be a SA URI. Publish the public key for 6.1.
oc -n tssc-admission create configmap lab-cosign-pubkey \
--from-file=cosign.pub=/home/lab-user/lab-trust/cosign.pub \
--dry-run=client -o yaml | oc apply -f -
# Tilde follows HOME. Put the real home back before the Check (or use /home/lab-user/lab-trust).
export HOME=/home/lab-user
Pass when ~/lab-trust/cosign.pub exists, sign exits 0, and ConfigMap tssc-admission/lab-cosign-pubkey has that PEM. Copying the worked-example path fails the Check.
Check: cosign verify --key
BUILD_NS="$(oc -n gitea get configmap demo-userinfo-gitea -o jsonpath='{.data.student_build_namespace}')"
IMAGE="image-registry.openshift-image-registry.svc:5000/${BUILD_NS}/spring-boot-lw-poc:latest"
export HOME=/home/lab-user
LAB_TRUST=/home/lab-user/lab-trust
test -f "${LAB_TRUST}/cosign.pub"
test ! -f /home/lab-user/example-not-lab-trust/demo.pub || echo 'example path present (ok if unused)'
cosign login -u unused -p "$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
image-registry.openshift-image-registry.svc:5000
cosign verify --key "${LAB_TRUST}/cosign.pub" \
--insecure-ignore-tlog \
--allow-http-registry --allow-insecure-registry \
"${IMAGE}"
Pass when that command exits 0 on your app digest using /home/lab-user/lab-trust/cosign.pub. 5.1 keyless verify must still be possible; do not strip the Fulcio signature.
The Job also grades ConfigMap report-14-disconnected-verify key diode_honest. Set it to not-air-gap. Empty or REPLACE_ME fails even if cluster state is correct. Do not copy validate-docs.
oc -n lw-poc-validate edit configmap report-14-disconnected-verify
The scored Check is Job validate-14-disconnected-verify 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-14-disconnected-verify --ignore-not-found
TPL='job-14.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-14-disconnected-verify
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.
Continue to Track 6 when you are ready.
Official documentation
-
Trusted Artifact Signer deployment guide (key-based
cosignalongside keyless) -
Disconnected transfer (mirrored TUF, not this Check)
Callout
TUF root.json and Fulcio/Rekor PEMs are not this Check. Pack them in the simulated media drop so you can say “mirrored TUF” without claiming this cluster is air-gapped.
Key takeaways
-
Track 5 is keyless and a key-based signature you created. That is portability, not a data diode.
-
Publish
cosign.pubtotssc-admission/lab-cosign-pubkey. 6.1 CRI-O admission uses it. -
The private key stays on Showroom. Never commit it to Git.
-
Lightwell Network and Clearinghouse Premier did not produce this key pair.
cd ~/ocp5-rhacs-showroom
bash setup/lab-cleanup.sh --module tssc-14