5.3 Disconnected verify

On this page you prove that the application digest also carries a signature you can verify with a public key, without calling Fulcio.

Track 5 is sign and attest. This is the third page. 5.1 Sign already signed that digest with Trusted Artifact Signer. You generate a cosign key pair on the Showroom terminal, run cosign sign --key, and then cosign verify --key. The cluster stays on the network. You are practicing the procedure a disconnected plant uses, which is a key you can carry. You are not building a physical air gap. Mirrored TUF for keyless verifiers, and a simulated media drop, live in Appendix: disconnected transfer.

Why this control exists

Keyless signing needs Fulcio when the signature is created, and a TUF root when it is verified. A plant that cannot reach Fulcio at verify time still needs a public key it controls. You add that second signature here, on the same digest. The keyless signature stays on the image.

Native ImagePolicy signedEmail cannot hold the 5.1 Kubernetes service-account URI. 6.1 GitOps + admission therefore renders CRI-O policy as policyType: PublicKey from the PEM you publish. That PEM is ConfigMap lab-cosign-pubkey in namespace tssc-admission. 5.1 Sign draws the keyless column. This page is the other signature: ~/lab-trust/cosign.key signs, and cosign.pub is what admission will trust.

Keyless Fulcio Rekor TUF path beside the lab key pair

The right column is this page. cosign generate-key-pair stays on the Showroom terminal, cosign sign --key signs the digest with --tlog-upload=false, and 6.1 reads cosign.pub as policyType: PublicKey.

Lightwell Network and Lightwell Clearinghouse Premier artifacts may already have their own signatures. This Check is the application image. Premier does not replace the key pair.

The private key stays on this terminal. It does not go in Git, and it does not go in a screenshot. cosign.key is the file to keep out of the commit.

In a customer engagement, a disconnected plant verifies with keys, and it keeps a mirrored TUF root for any artifact that was signed keylessly. You generate the pair on this terminal and sign the digest you already built. Red Hat does not operate a data diode on this cluster. An empty COSIGN_PASSWORD is for this non-interactive lab terminal. A customer plant uses a real passphrase.

Learning objectives

By the end of this module, you will be able to:

  • Generate a cosign key pair under /home/lab-user/lab-trust and sign the application digest with --key and --tlog-upload=false

  • Verify that digest with cosign verify --key

  • Publish cosign.pub to ConfigMap lab-cosign-pubkey so 6.1 can render ImagePolicy

  • Explain that a Lightwell library signature is a different layer from this application key

The digest you already signed

cosign is already on the Showroom PATH. Success is a dockerImageReference for spring-boot-lw-poc:latest in the build namespace, and an empty ~/lab-trust before you generate the key.

Read the 5.1 digest
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)'

The block below verifies a different image with a key in a different directory. Using ~/example-not-lab-trust/demo.pub as the scored key fails 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

Generate the key pair in ~/lab-trust if cosign.key is not already there. export HOME=/tmp/lab-home exists so cosign login does not write a Docker config into the real home directory. The --key path and the ConfigMap --from-file path stay on /home/lab-user/lab-trust, because a tilde would follow the temporary HOME. --tlog-upload=false keeps this signature off the public Rekor log. That is the disconnected procedure: the signature travels with the digest, and verify uses --insecure-ignore-tlog on the next step.

ImagePolicy signedEmail cannot store a service-account URI. The public key is what 6.1 will render. Success is cosign.pub on disk, cosign sign exiting 0, and ConfigMap lab-cosign-pubkey in namespace tssc-admission holding that PEM. Put HOME back to /home/lab-user before the Check.

Sign with the key and publish the public 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"
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

Check

Verify the same application tag with /home/lab-user/lab-trust/cosign.pub. --insecure-ignore-tlog matches the sign, which did not upload a transparency-log entry. Success is exit code 0 on your application digest. The 5.1 keyless signature has to remain verifiable. Stripping the Fulcio signature fails that later use.

Verify with the public 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}"
Fill the report

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.

Run the Validate Job
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.

The Validate Job reads ConfigMap tssc-admission/lab-cosign-pubkey and runs cosign verify --key on ImageStream tag spring-boot-lw-poc:latest in the build namespace. The PEM in that ConfigMap has to be the public key that signed the digest. REPLACE_ME fails the Check. Publishing ~/example-not-lab-trust/demo.pub fails it too. Task cosign-sign-keyless is the 5.1 Sign Check. Track 6 still opens if you skip this page. TUF root.json and the Fulcio and Rekor PEMs are a different exercise. Pack them in the simulated media drop when you want to say "mirrored TUF" about a keyless artifact. This page’s artifact verifies with cosign.pub.

Official documentation

Key takeaways

  • Track 5 is the keyless signature and a key-based signature you created on the same digest.

  • cosign.pub is published to tssc-admission/lab-cosign-pubkey. 6.1 admission uses it.

  • The private key stays on the Showroom terminal.

  • The cluster stayed on the network. The procedure is what a disconnected verify looks like.