Audit trails & attestation
Tamper-evident, attested, reusable evidence.
An auditor opens your SOC 2 (Service Organization Control 2, the audit report your enterprise customers ask for) evidence folder and finds hundreds of Conftest and OPA (Open Policy Agent, the engine those policy checks run on) results. All green. All timestamped. Then comes the one question that flattens the whole pile: "How do I know this file describes the image you actually deployed, and that nobody flipped a FAIL to a PASS afterwards?" The finding they wrote up was blunt. Evidence collection is automated, but the pipeline runs zero attestation commands. Nothing ties a verdict to the artifact it judged, and nothing makes tampering visible. Evidence that anyone with write access can quietly edit is a spreadsheet, not proof. This lesson closes that hole by turning a compliance result into a signed, publicly logged in-toto attestation an auditor can check without trusting you at all.
From evidence to attested evidence
A notarized letter beats a photocopy for one reason. Someone you both trust stamped it, tied that stamp to that exact page, and wrote the act into a register anyone can look up later. An attestation does the same job for software. It is a signed, machine-checkable statement about one specific artifact. The format the industry settled on is an in-toto Statement, a small JSON (plain-text data) wrapper with three parts. The subject is the artifact, named by its content digest (a sha256 fingerprint of the actual bytes, never a movable tag). The predicateType is a URI (a web address used as a label) saying what kind of claim this is. The predicate is the claim body itself, here your control-by-control compliance results. Cosign wraps that Statement in a DSSE envelope (Dead Simple Signing Envelope), which signs the payload together with its type, so a signature over compliance results can never be replayed to mean something else. Signing is keyless. Instead of keeping a long-lived private key that can leak, cosign trades your CI (continuous integration) system's OIDC (OpenID Connect, the standard that lets one service prove who it is to another) token for a certificate from Fulcio that lives about ten minutes, uses it once, and throws the key away. It then writes the signature into Rekor, Sigstore's public append-only log. Rekor is the courthouse register in that analogy. Every entry is timestamped and carries an inclusion proof, so nothing can be altered, backdated or removed without the change showing. Five words carry the entire trust chain: subject, predicate, DSSE, Fulcio, Rekor.
Step 1: produce the compliance predicate
Start from a real evaluation, not a hand-written summary. Your Conftest run over a rendered manifest already emits a pass or fail for every rule. Capture that as a structured file keyed to control IDs, and pin it to the exact image digest you are about to sign. Keep it small and deterministic, so two runs over the same manifest produce the same bytes. This file is the durable record an auditor reads once the signature is decoded, which means it has to make sense on its own, months later, to someone who has never seen your repository.
{"control_set": "CIS Kubernetes Benchmark v1.9","evaluated_at": "2026-07-14T09:12:44Z","evaluator": "conftest 0.56.0 / opa 0.68.0","subject_digest": "sha256:5b21e0c1f4c9a4d3e77b2a1f9c0e8d6b4a2c1e0f9d8c7b6a5e4f3d2c1b0a9f8e","results": [{ "control": "5.2.7", "requirement": "Minimize root containers", "status": "PASS" },{ "control": "5.2.2", "requirement": "Disallow privileged containers", "status": "PASS" },{ "control": "5.7.3", "requirement": "Apply a SecurityContext", "status": "PASS" }],"summary": { "pass": 3, "fail": 0 }}
Step 2: sign it as an in-toto attestation
Install cosign, turn the tag into the digest it currently points at, then attest that digest. Attesting a tag is close to worthless, because a tag is a sticky note that anyone with push access can peel off and stick on a different image. In CI there is no secret to manage. Cosign reads the runner's OIDC token, gets a Fulcio certificate bound to the workflow identity, wraps compliance.json in an in-toto Statement of predicateType custom, signs the DSSE envelope, and uploads the entry to Rekor. The tlog (transparency log) index printed at the end is your permanent public receipt, and anyone can look that number up years later. COSIGN_YES=true answers the transparency-log privacy notice for you, so an unattended pipeline does not sit there forever waiting for someone to type y.
# 1. install a pinned cosign (Linux amd64) — never 'latest' in CIcurl -sSfL -o cosign \https://github.com/sigstore/cosign/releases/download/v2.4.1/cosign-linux-amd64chmod +x cosign && sudo mv cosign /usr/local/bin/cosigncosign version | head -1# 2. resolve tag -> immutable digest, then attest that digestIMAGE=registry.acme.dev/payments/api:1.4.2DIGEST=$(crane digest "$IMAGE") # sha256:5b21e0c1...REF="registry.acme.dev/payments/api@${DIGEST}"COSIGN_YES=true cosign attest \--predicate compliance.json \--type custom \"$REF"
Generating ephemeral keys...Retrieving signed certificate...The sigstore service, hosted by sigstore a Series of LF Projects, LLC, is provided pursuant to the Hosted Project Tools Terms of Use, available at https://lfprojects.org/policies/hosted-project-tools-terms-of-use/.Note that if your submission includes personal data associated with this signed artifact, it will be part of an immutable record.This may include the email address associated with the account with which you authenticate your contractual Agreement.This information will be used for signing this artifact and will be stored in public transparency logs and cannot be removed later, and is subject to the Immutable Record notice at https://lfprojects.org/policies/hosted-project-tools-immutable-records/.By typing 'y', you attest that (1) you are not submitting the personal data of any other person; and (2) you understand and agree to the statement and the Agreement terms at the URLs listed above.tlog entry created with index: 158843201
Step 3: verify the signed evidence chain
A doorman who confirms that your ID card exists has checked nothing. The one who reads the name on it and the government that issued it has. Verification works the same way. The question is whether this was signed by the identity you trust, over the image you are about to run, and whether the signature sits in the public log. cosign verify-attestation checks the DSSE signature against Fulcio's root, confirms an inclusion proof exists in Rekor (offline, from the proof bundled with the signature), and accepts only certificates whose identity and issuer match the constraints you hand it. Pin --certificate-identity to your exact release workflow and --certificate-oidc-issuer to your OIDC provider. Any mismatch exits non-zero, which is what lets this one command sit in a deploy gate and stop a release. On success it prints the DSSE envelope, and you decode the payload to read which controls passed. Fulcio and Rekor are public, so an auditor can run the same command from their own laptop. That is why they accept the answer. Nothing about it depends on trusting you or your infrastructure.
cosign verify-attestation \--type custom \--certificate-identity 'https://github.com/acme/payments/.github/workflows/release.yml@refs/tags/v1.4.2' \--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \"$REF" > attestation.jsonecho "verify exit code: $?"# the custom predicate is nested as .predicate.Data (a JSON string) — decode itjq -r '.payload | @base64d | fromjson| .predicate.Data | fromjson | .summary' attestation.json
Verification for registry.acme.dev/payments/api@sha256:5b21e0c1... --The following checks were performed on each of these signatures:- The cosign claims were validated- Existence of the claims in the transparency log was verified offline- The code-signing certificate was verified using trusted certificate authority certificatesCertificate subject: https://github.com/acme/payments/.github/workflows/release.yml@refs/tags/v1.4.2Certificate issuer URL: https://token.actions.githubusercontent.comverify exit code: 0{"pass": 3,"fail": 0}
A verified attestation gives you a strong, reusable, auditor-acceptable fact: this control passed at build time, against this exact digest, signed by this workflow. What it says nothing about is 3 a.m. three weeks later, when someone widens a security group by hand or patches a live Deployment in place. The distance between the digest you attested and the state actually running is itself a compliance finding, and the next lesson, Drift as a compliance signal, shows how to collect it.
Try this
Work through “Step 3: verify the signed evidence chain” yourself on a sandbox you can throw away, following the commands above in order. Then break one step deliberately and re-run, so you have seen the failure before it finds you.
Takeaway
The trap worth remembering here: verification without a pinned identity accepts any Sigstore signature. Check that on your own systems before you need to, because it is cheaper to find on a quiet afternoon than during an incident.