7 minute read Software Engineering, Security

Signing container images is one of the highest-leverage things you can do to improve your software supply chain. A signature gives you a cryptographic way to answer “who produced this image?” and “has it been tampered with?”. Attestations (for example: provenance and SBOMs) help you answer deeper questions like “how was it built?” and “what’s inside it?”.

This post is a practical reference for:

  • Signing OCI container images with cosign (Sigstore)
  • Generating and attaching attestations (including SBOM-related examples)
  • Storing signatures/attestations alongside images in Azure Container Registry (ACR)
  • Understanding how the OCI Referrers API organises “artifacts about artifacts”
  • Briefly comparing Cosign to Notation (Notary Project)

Key concepts (what are we actually storing?)

When people say “sign the image” they often mean a few related, but distinct things.

Signatures

A signature is a statement like:

“I (an identity) signed this immutable image digest.”

The important part is that you sign the image digest (@sha256:...), not the tag (:latest), because tags are mutable.

Attestations

An attestation is a signed statement about the image (or about another artifact attached to the image). Examples:

  • Provenance: where the build ran, what sources were used, what inputs were consumed
  • SBOM: a machine-readable list of packages/components contained in the image
  • Vulnerability scan results (careful: these go stale quickly)

Cosign signs attestations using DSSE envelopes and commonly uses the in-toto attestation format.

SBOMs

An SBOM is a document — it’s valuable when you:

  • Generate it consistently
  • Store it alongside the exact digest you shipped
  • Sign it (so people can trust its origin)
  • Make it discoverable by automation

Why sign images and generate attestations?

If you’re already scanning images, signatures and attestations provide the missing chain of custody.

  • Integrity: detect tampering (a signature ties to an immutable digest)
  • Authenticity: verify “this came from our pipeline/team”, not just “it exists in our registry”
  • Policy enforcement: gate deployments on verified signatures/attestations (Kubernetes admission, CI/CD checks)
  • Auditability: prove what was deployed and when, and how it was built
  • Better incident response: quickly answer “which builds include vulnerable component X?” when you have SBOMs attached to digests

Where do signatures/attestations live? OCI Artifacts + the Referrers API

Modern container signing doesn’t embed signatures “inside” an image. Instead, registries store additional OCI artifacts that refer to the image.

The OCI Distribution Spec defines a Referrers API (OCI v1.1) that lets a client ask:

“List all artifacts that refer to this subject digest.”

That makes signatures, SBOMs, provenance, scan reports, etc. first-class, discoverable artifacts associated with an image.

Azure Container Registry supports storing and discovering these supply-chain artifacts. Tools like ORAS can show the attached artifact graph, and ACR supports the OCI Referrers API in most scenarios.

Note: some environments may fall back to the older “referrers tag schema” approach instead of the Referrers API. For example, ACR’s documentation notes that some features (notably customer-managed key encrypted registries) may require fallback behavior.

Cosign vs Notation (quick comparison)

Both Cosign and Notation ultimately store “signatures as OCI artifacts” in registries, but they’re optimised for different workflows.

Cosign (Sigstore)

  • Excellent developer experience for keyless signing using OIDC identities (Fulcio certificates) and transparency logging (Rekor)
  • Strong support for attestations (provenance, SBOM predicates, etc.)
  • Works well in modern CI/CD environments where identity is provided by an OIDC issuer (GitHub Actions, etc.)

Notation (Notary Project)

  • Industry standardisation effort for OCI signing with an emphasis on X.509 / trust policies
  • Strong fit for enterprise key management, including KMS/HSM-backed keys (for example via an Azure Key Vault plugin)
  • Focused primarily on signing/verifying artifacts; attestations are commonly handled via separate OCI artifacts and tooling

If you’re building a modern “provenance + SBOM + policy” workflow, Cosign is often the fastest path. If you need a standardised signature format and strong enterprise policy/trust-store modeling (and/or a vendor-supported path), Notation is worth serious consideration.

Walkthrough: build, push, sign, and attach attestations in ACR

This walkthrough uses:

  • Azure Container Registry (ACR)
  • cosign for signing and attestations
  • oras to discover and reason about the artifact graph (Referrers API)

Prerequisites

  • Azure CLI (az) authenticated to your subscription
  • ACR with permissions to push images and artifacts
  • Tools:
    • cosign
    • oras
    • jq (optional, but helpful)
    • SBOM generator such as syft (optional)

On macOS you can typically install these with Homebrew (adjust as needed):

brew install cosign oras jq syft

1) Create an ACR and sign in

az login

export LOCATION=uksouth
export RG=rg-acr-signing-demo
export ACR_NAME=<your-unique-acr-name>

az group create -n "$RG" -l "$LOCATION"
az acr create -g "$RG" -n "$ACR_NAME" --sku Standard

export REGISTRY="$ACR_NAME.azurecr.io"
az acr login -n "$ACR_NAME"

2) Build and push an image (and capture the digest)

You can build locally with Docker and push, but for a clean “works anywhere” demo, ACR Tasks is handy.

export REPO=demo/net-monitor
export TAG=v1

# Example source repo used in Microsoft docs; swap this for your own Dockerfile repo.
export IMAGE_SOURCE=https://github.com/wabbit-networks/net-monitor.git#main

DIGEST=$(az acr build \
	-r "$ACR_NAME" \
	-t "$REGISTRY/${REPO}:$TAG" \
	"$IMAGE_SOURCE" \
	--no-logs \
	--query "outputImages[0].digest" -o tsv)

export IMAGE_DIGEST="$REGISTRY/${REPO}@$DIGEST"
echo "$IMAGE_DIGEST"

From here on, use $IMAGE_DIGEST (digest reference) for signing and attestations.

3) Sign the image with Cosign (keyless)

Keyless signing is Cosign’s default “batteries included” path.

cosign sign --yes "$IMAGE_DIGEST"

What to expect:

  • A browser-based OIDC flow (unless you’re already in a CI environment with an OIDC token)
  • A short-lived signing certificate issued by Sigstore’s Fulcio
  • A transparency log entry in Rekor (by default)
  • A signature stored in your registry as an OCI artifact referring to the image

4) Verify the Cosign signature

Verification should be part of your CI and/or cluster admission policy. The important idea is: verify not only “is it signed?” but “is it signed by the identity we trust?”.

For interactive keyless signing, the OIDC issuer is commonly:

OIDC_ISSUER="https://oauth2.sigstore.dev/auth"

And the identity is typically the OIDC subject (often your email). Example:

cosign verify \
	--certificate-identity "you@example.com" \
	--certificate-oidc-issuer "$OIDC_ISSUER" \
	"$IMAGE_DIGEST"

In CI (for example GitHub Actions), you’d instead verify against that workflow identity and issuer (for example https://token.actions.githubusercontent.com), often using the --certificate-identity-regexp flags to match your org/repo/workflow.

5) Generate an SBOM and attach it as an attestation (Cosign)

One straightforward pattern is:

1) Generate an SBOM file 2) Attest to it (sign a statement that includes it)

Generate an SBOM with Syft (SPDX JSON example):

syft packages "$IMAGE_DIGEST" -o spdx-json=sbom.spdx.json

Create an attestation for that SBOM:

cosign attest --yes \
	--predicate sbom.spdx.json \
	--type spdxjson \
	"$IMAGE_DIGEST"

Verify the attestation:

cosign verify-attestation \
	--certificate-identity "you@example.com" \
	--certificate-oidc-issuer "$OIDC_ISSUER" \
	--type spdxjson \
	"$IMAGE_DIGEST" \
	| jq -r '.[0].payload' \
	| base64 --decode

6) Discover what’s attached (OCI Referrers) with ORAS

This is the piece most people miss: once signatures/SBOMs/attestations are stored as OCI artifacts, you can query the graph.

oras discover -o tree "$IMAGE_DIGEST"

You should see the subject image at the top, with child artifact types beneath it (signatures, attestations, and any other artifacts you attach).

Alternative pattern: store the SBOM as an OCI artifact, then sign that

Sometimes you want the SBOM itself stored as a first-class artifact in the registry (so it can be pulled directly), and then you sign/attest to that artifact. OCI referrers support deep graphs: image → SBOM → signature.

Attach an SBOM file to the image as a referenced OCI artifact:

oras attach "$IMAGE_DIGEST" \
	--artifact-type sbom/example \
	./sbom.spdx.json:application/json

Discover again:

oras discover -o tree "$IMAGE_DIGEST"

This pattern is powerful when you want:

  • direct pull of SBOMs (oras pull), separate from verification tooling
  • to sign the SBOM artifact independently (for example with Notation)

Promoting images with their signatures/attestations

If you promote images across registries/environments, you should promote the artifact graph, not only the image manifest.

With ORAS you can copy an image and its referrers:

export TARGET_REPO="$REGISTRY/staging/$REPO"
oras copy -r "$IMAGE_DIGEST" "$TARGET_REPO:$TAG"

Then:

oras discover -o tree "$TARGET_REPO:$TAG"

Notation (brief practical example in Azure)

If you’re evaluating Notation in Azure, Microsoft provides a walkthrough that signs images in ACR using keys held in Azure Key Vault. The high-level flow looks like:

1) Install Notation + the Azure Key Vault plugin 2) Build/push image and sign by digest 3) Add certs to a trust store and create a trust policy 4) Verify

Signing example (Key Vault plugin; simplified):

notation sign \
	--signature-format cose \
	--id "$KEY_ID" \
	--plugin azure-kv \
	--plugin-config self_signed=true \
	"$IMAGE_DIGEST"

And list signatures:

notation ls "$IMAGE_DIGEST"

An important nuance called out in Microsoft’s guidance: Notation can store signatures using the referrers tag schema by default, and can also use the OCI Referrers API depending on configuration/registry capabilities.

Common pitfalls

  • Always sign by digest (@sha256:...), not by tag.
  • Verification must include identity constraints, not only “is signed?”.
  • Plan where your trust roots live: developer keyless identity, CI workload identity, or enterprise PKI (Notation) are different trust models.
  • Think about artifact lifecycle: deleting an image doesn’t always automatically delete every related artifact in every registry implementation.
  • Referrers API vs tag schema: some environments may require fallback behavior; test your exact ACR configuration (including encryption features) if you’re standardising on Referrers API workflows.

Suggested “good default” pipeline shape

If you’re starting from scratch, a practical baseline is:

1) Build image 2) Push image 3) Generate SBOM 4) Create provenance (if your build system supports it) 5) Sign image digest 6) Attest SBOM/provenance to image digest 7) Enforce verification at deploy time (admission policy)

If you want to go further, add:

  • environment-specific signatures (promotion gates)
  • oras copy -r promotion of artifact graphs
  • key management hardening (HSM/KMS), timestamping, and policy-as-code

Leave a comment