5.2 Attest + Conforma
On this page you prove that policy can fail an unsigned image and pass the application digest you signed.
Track 5 is sign and attest. This is the second page. Konflux calls this gate Conforma, the Enterprise Contract. This cluster maps it to Task conforma-policy in namespace lightwell-tasks. The Task is not in the seeded pipeline. The seeded ConfigMap is too permissive: signature checks are skipped, the identity is .*, and the CVE ceiling is 999. Argo CD owns that object and reverts edits to it. You copy the ConfigMap into lw-poc-build and tighten the copy.
The Check is fail-then-pass on the image signature and on the policy you own. It is not an SLSA L3 attestation. This cluster has no Tekton Chains and no cosign attest step. Leave skip-att-sig-check=true. Showroom ec is 0.7 (ec-rhel9:0.7). That build has no --skip-att-sig-check flag, and --ignore-rekor breaks Fulcio timestamps on the pass path. You ignore only builtin.attestation.signature_check when you read the report. The term map is Appendix: Konflux mapping.
Why this control exists
A pipeline that always passes is a demo. The unsigned in-cluster image openjdk-21-builder violates builtin.image.signature_check. The digest you signed on 5.1 Sign passes. You own the policy that makes that distinction.
The copy is the object you edit. skip-image-sig-check becomes false. The certificate identity is the pipeline service-account regexp from 5.1, not . and not a Hummingbird placeholder. The OIDC issuer regexp is https://kubernetes.default.svc. The registry prefix is the internal registry. The catch-all exclude, a list whose only entry is , comes out. restrict_max_cve_score comes down from 999. skip-att-sig-check stays true, because there is no attestation to check. An exclude entry for attestation_signature_check does nothing on ec 0.7: the builtins ignore that list.
The Task runs after cosign-sign-keyless. A task that runs before the signature has nothing to accept.
Lightwell Network and Lightwell Clearinghouse Premier do not replace Conforma. Library VEX is Track 7. This gate is the image signature and the signer identity. A Premier GAV inside an unsigned image still fails.
The figure is the copy. The left box is the Argo-owned seed. The right box is the ConfigMap in lw-poc-build after you tighten it. Edit the copy. The original reverts. The keys you write are the ones in Your change: restrict_max_cve_score comes down from 999, and allowed_registry_prefixes is image-registry.openshift-image-registry.svc.
|
SLSA L3 and Tekton Chains are the names a customer may use for a build provenance attestation. This cluster does not run that path. The mapping table is in Konflux mapping. |
In a customer engagement on hosted Konflux, Conforma is attached to the build. Here the control is a namespaced Task plus a ConfigMap you tightened. Fetching a policy bundle from quay.io or github.com fails the Check.
Learning objectives
By the end of this module, you will be able to:
-
Copy ConfigMap
conforma-policyinto the build namespace and tighten the copy -
Wire Task
conforma-policyaftercosign-sign-keyless, pointed at that copy -
Show
ec validatereportingbuiltin.image.signature_checkonopenjdk-21-builder -
Show the signed application digest passing that same check, with only the attestation builtin left over
The seed you copy
Success is the Task in lightwell-tasks, a seed whose skip-image-sig-check is still on and whose identity is still .*, and a documentation snippet you will not paste.
oc -n lightwell-tasks get task conforma-policy
oc -n lightwell-tasks get configmap conforma-policy \
-o jsonpath='{.data.skip-image-sig-check}{"\n"}{.data.certificate-identity-regexp}{"\n"}'
oc -n lightwell-tasks get configmap conforma-policy-docs \
-o jsonpath='{.data.example-pipeline-snippet\.yaml}'
The block below runs before the signature, under a different task name, against a public image. A paste of it fails the Check.
# Worked example ONLY — wrong Task name, runs before sign, public image.
- name: verify-enterprise-contract
runAfter: [clone]
params:
- name: POLICY_CONFIGURATION
value: example.invalid/not-tightened
- name: IMAGES
value: docker.io/library/java:latest
Your change
Copy ConfigMap conforma-policy from lightwell-tasks into the build namespace. The sed drops the identity fields Argo and the API server own, and rewrites the namespace. Success is the ConfigMap present in lw-poc-build.
BUILD_NS="$(oc -n gitea get configmap demo-userinfo-gitea -o jsonpath='{.data.student_build_namespace}')"
oc -n lightwell-tasks get configmap conforma-policy -o yaml \
| sed -e '/^ resourceVersion:/d' -e '/^ uid:/d' -e '/^ creationTimestamp:/d' \
-e "s/namespace: lightwell-tasks/namespace: ${BUILD_NS}/" \
| oc apply -f -
On that copy, set skip-image-sig-check to false. Leave skip-att-sig-check as true. Set certificate-identity-regexp to the pipeline service-account regexp from 5.1. Set the OIDC issuer regexp to https://kubernetes.default.svc. In policy.yaml and data.yaml, remove the catch-all exclude. Lower restrict_max_cve_score below 999. Set allowed_registry_prefixes to image-registry.openshift-image-registry.svc.
In .tekton/pipeline.yaml on the learner remote, add Task conforma-policy. Resolve it from the cluster, namespace lightwell-tasks, with runAfter: [cosign-sign-keyless]. Point policy-namespace and policy-configmap at your copy in lw-poc-build. Pass tuf-url: $(params.cosign-tuf-url) and rekor-url: $(params.cosign-rekor-url). Those parameters are the in-cluster *.trusted-artifact-signer.svc URLs from 4.3 and 5.1. Commit, push, and apply the Pipeline.
Create a PipelineRun from the filled .tekton/pipelinerun.yaml after that apply. metadata.generateName starts a new run and leaves the 4.4 run in place. The Check reads a succeeded TaskRun for conforma-policy. That task runs after cosign-sign-keyless, so the sign task has to succeed first. Wait until both tasks show Succeeded. A Fulcio error on the sign task is still 5.1 Sign, and this page stays red until that signature exists.
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
RUN="$(oc -n "${BUILD_NS}" get pipelinerun --sort-by=.metadata.creationTimestamp \
-o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' | awk 'NF { last=$0 } END { print last }')"
echo "waiting on ${RUN}"
oc -n "${BUILD_NS}" wait --for=condition=Succeeded "pipelinerun/${RUN}" --timeout=20m || true
oc -n "${BUILD_NS}" get taskrun -l "tekton.dev/pipelineRun=${RUN}" \
-o 'custom-columns=TASK:.metadata.labels.tekton\.dev/pipelineTask,SUCCEEDED:.status.conditions[0].status,REASON:.status.conditions[0].reason'
Check
The fail image is ImageStream openjdk-21-builder in the build namespace, the image you imported on 4.1. It is not signed by the pipeline service account, and the internal registry is allowed to pull it. The pass image is spring-boot-lw-poc:latest from 5.1. docker.io/library/java:latest is the worked example, and hermetic egress also blocks it from the build namespace.
ec is on the Showroom PATH. Initialize TUF and log in to the internal registry the same way as 5.1. Leave --ignore-rekor and --skip-att-sig-check off the command. --strict=false writes a report even when the attestation builtin fails. You judge the files, not the exit code.
Success is five facts. The builder report contains builtin.image.signature_check as a [Violation]. The application report has no [Violation] left once builtin.attestation.signature_check is set aside. The live Pipeline includes conforma-policy after cosign-sign-keyless, with in-cluster tuf-url and rekor-url. policy-namespace is lw-poc-build. The conforma-policy TaskRun from the run above is Succeeded.
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}')"
mkdir -p /tmp/ec /tmp/lab-home
export HOME=/tmp/lab-home
export SSL_CERT_FILE=/var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt
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
oc -n "${BUILD_NS}" extract configmap/conforma-policy --to=/tmp/ec --confirm
FAIL_IMG="image-registry.openshift-image-registry.svc:5000/${BUILD_NS}/openjdk-21-builder:latest"
PASS_IMG="image-registry.openshift-image-registry.svc:5000/${BUILD_NS}/spring-boot-lw-poc:latest"
IDENTITY="$(tr -d '[:space:]' < /tmp/ec/certificate-identity-regexp)"
ISSUER="$(tr -d '[:space:]' < /tmp/ec/certificate-oidc-issuer-regexp)"
# Fail path: unsigned builder must report builtin.image.signature_check
ec validate image --image="${FAIL_IMG}" --policy=/tmp/ec/policy.yaml --strict=false \
--show-successes --info --output=text=/tmp/ec-fail.txt \
--certificate-identity-regexp="${IDENTITY}" \
--certificate-oidc-issuer-regexp="${ISSUER}" \
--rekor-url="${REKOR}" || true
grep '\[Violation\]' /tmp/ec-fail.txt | grep -q 'builtin.image.signature_check' \
|| { echo 'FAIL PATH UNEXPECTEDLY PASSED'; cat /tmp/ec-fail.txt; exit 1; }
# Pass path: ignore only the attestation builtin (no cosign attest on this cluster)
ec validate image --image="${PASS_IMG}" --policy=/tmp/ec/policy.yaml --strict=false \
--show-successes --info --output=text=/tmp/ec-pass.txt \
--certificate-identity-regexp="${IDENTITY}" \
--certificate-oidc-issuer-regexp="${ISSUER}" \
--rekor-url="${REKOR}" || true
grep '\[Violation\]' /tmp/ec-pass.txt | grep -q 'builtin.image.signature_check' \
&& { echo 'PASS PATH: image signature still failing'; cat /tmp/ec-pass.txt; exit 1; }
leftover="$(grep '\[Violation\]' /tmp/ec-pass.txt | grep -v 'builtin.attestation.signature_check' || true)"
[[ -z "${leftover}" ]] || { echo "PASS PATH leftover: ${leftover}"; cat /tmp/ec-pass.txt; exit 1; }
oc -n "${BUILD_NS}" get pipeline spring-boot-lw-poc-build-sign -o yaml \
| grep -A30 'name: conforma-policy' | head -40
The capture is the two reports. The builder lists builtin.image.signature_check as a violation. The application lists that same check as a success. Both reports can still show builtin.attestation.signature_check as a violation, and the pass report can still say Success: false, because this cluster has no cosign attest. The script above treats that attestation line as expected and fails the page on any other leftover violation.
The Job also grades ConfigMap report-13-attest-conforma key policy_owner. Set it to learner-copy. Empty or REPLACE_ME fails even if cluster state is correct. Do not copy validate-docs.
oc -n lw-poc-validate edit configmap report-13-attest-conforma
The scored Check is Job validate-13-attest-conforma 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-13-attest-conforma --ignore-not-found
TPL='job-13.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-13-attest-conforma
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 tightened copy, the Task order, and a succeeded conforma-policy TaskRun. 5.3 Disconnected verify and Track 6 still open if you skip this page.
Official documentation
-
Trusted Artifact Signer deployment guide (Conforma /
ecagainst Trusted Artifact Signer signatures) -
Konflux mapping (Conforma maps to Task
conforma-policy)
Key takeaways
-
The policy that can fail is the copy you tightened in
lw-poc-build. -
The unsigned builder fails
builtin.image.signature_check. The signed application digest passes it. -
Leave
skip-att-sig-check=true, and ignorebuiltin.attestation.signature_checkin the report. -
Lightwell Network and Clearinghouse Premier do not skip this gate.

