|
| 1 | +<!-- |
| 2 | +--- |
| 3 | +linkTitle: "OCI Artifact Distribution (Referrers)" |
| 4 | +weight: 35 |
| 5 | +--- |
| 6 | +--> |
| 7 | + |
| 8 | +# OCI Artifact Distribution: the Referrers Schema |
| 9 | + |
| 10 | +When Chains is configured with `oci` as the artifact storage backend, it signs the resulting image and uploads the signature (`.sig`) and SLSA Provenance (`.att`) for the TaskRun or PipelineRun to the configured OCI repository. As OCI registries adopt the [OCI Distribution Spec v1.1.1](https://specs.opencontainers.org/distribution-spec/?v=v1.1.1), [PR#1691](https://github.com/tektoncd/chains/pull/1691) introduced support for uploading these blobs in the Referrers API format, when the registry supports it. This page explains the additional configuration required to adopt the new approach. |
| 11 | + |
| 12 | +If you don't change anything, Chains uses the older tag-based layout and just works. Proceed reading further, if you want to use the OCI 1.1 Referrers API instead. |
| 13 | + |
| 14 | +## The problem with the old layout |
| 15 | + |
| 16 | +cosign, which Chains uses under the hood, has traditionally stored a signature |
| 17 | +or attestation by pushing an extra tag next to the image. For an image at digest |
| 18 | +`sha256:abc...`, it creates tags like `sha256-abc....sig` and `sha256-abc....att`. |
| 19 | + |
| 20 | +This works on every registry, but it has drawbacks: |
| 21 | + |
| 22 | +- Tags are meant to name top-level artifacts you look up and pull. Reusing them |
| 23 | + for supporting metadata (signatures, attestations) puts that metadata in the |
| 24 | + same namespace as the real artifacts, where it is easy to confuse the two. |
| 25 | +- The `.att` tag points to a single manifest that holds *all* of an image's |
| 26 | + attestations, and that manifest has no stable digest. When another process |
| 27 | + adds an attestation, they are folded back into the same manifest and its |
| 28 | + digest changes, so there is no stable, individually addressable reference to a |
| 29 | + single attestation. |
| 30 | +- No OCI standard describes this layout, so every tool has to special-case it. |
| 31 | + |
| 32 | +## What the Referrers schema is |
| 33 | + |
| 34 | +The [OCI 1.1 distribution spec](https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#listing-referrers) |
| 35 | +added a standard way to attach one artifact to another. Instead of inventing a |
| 36 | +tag, you push the signature or attestation as its own manifest that records the |
| 37 | +image it belongs to in a `subject` field. The registry can then answer a simple |
| 38 | +question: "what artifacts refer to this image?" |
| 39 | + |
| 40 | +This is the **Referrers schema**. The payoff is no extra tags, a clean registry, |
| 41 | +and a standard that registries and policy tools already understand. |
| 42 | + |
| 43 | +## How cosign enables it |
| 44 | + |
| 45 | +cosign and the `go-containerregistry` library it uses both understand the |
| 46 | +Referrers schema, and Chains drives them directly through that library rather |
| 47 | +than shelling out to the cosign CLI. When Chains writes in referrers mode, the |
| 48 | +library prefers the registry's native Referrers API if it is available. If the |
| 49 | +registry does not expose it, the library falls back to the spec's **referrers |
| 50 | +tag schema**: it maintains a single `sha256-<digest>` index tag that lists the |
| 51 | +referrers. |
| 52 | + |
| 53 | +Either way it is still "referrers mode" — the stored content is the same, no |
| 54 | +`.sig` or `.att` tags are created, and `cosign verify` and `oras discover` both |
| 55 | +work. This fallback is automatic and needs no configuration, so it also covers |
| 56 | +registries that don't yet have native support. |
| 57 | + |
| 58 | +## How Chains enables it |
| 59 | + |
| 60 | +Chains exposes this through a single config flag in the `chains-config` |
| 61 | +ConfigMap: |
| 62 | + |
| 63 | +```yaml |
| 64 | +apiVersion: v1 |
| 65 | +kind: ConfigMap |
| 66 | +metadata: |
| 67 | + name: chains-config |
| 68 | + namespace: tekton-chains |
| 69 | +data: |
| 70 | + storage.oci.distribution-method: "referrers-api" |
| 71 | +``` |
| 72 | +
|
| 73 | +| Value | What Chains does | |
| 74 | +|---|---| |
| 75 | +| `legacy` (default) | Old tag-based layout (`.sig` / `.att` tags), DSSE-encoded. Works everywhere. | |
| 76 | +| `referrers-api` | OCI 1.1 Referrers schema. No extra tags, with automatic fallback on registries that lack native support. | |
| 77 | + |
| 78 | +That's the only knob. You pick *where* artifacts go, and Chains picks the right |
| 79 | +encoding to match (explained next). |
| 80 | + |
| 81 | +> [!NOTE] |
| 82 | +> This flag only takes effect when the OCI storage backend is in use — that is, |
| 83 | +> when `artifacts.oci.storage`, `artifacts.taskrun.storage`, or |
| 84 | +> `artifacts.pipelinerun.storage` includes `oci`. If you store signatures and |
| 85 | +> attestations somewhere else (docstore, mongo, Grafeas or other options supported in chains), `storage.oci.distribution-method` has no effect. |
| 86 | + |
| 87 | +> [!TIP] |
| 88 | +> Chains vendors the Sigstore libraries it needs, so referrers mode works out of |
| 89 | +> the box — there is nothing extra to install. You only need a separate `cosign` |
| 90 | +> or `oras` CLI if you want to verify or inspect the stored artifacts yourself, |
| 91 | +> as shown below. |
| 92 | + |
| 93 | +## Why the encoding is tied to the layout |
| 94 | + |
| 95 | +The two choices line up with cosign's own defaults as they have evolved: |
| 96 | + |
| 97 | +- **legacy** matches cosign's original default — tag-based storage with the |
| 98 | + attestation written as a **DSSE** envelope. This is what existing tooling has |
| 99 | + always verified. |
| 100 | +- **referrers-api** writes the attestation over the OCI 1.1 Referrers API as a |
| 101 | + **Sigstore protobuf bundle**, the format newer cosign versions write and |
| 102 | + verify. |
| 103 | + |
| 104 | +Chains deliberately ties the encoding to the layout instead of exposing it as a |
| 105 | +separate switch. The reason is compatibility: cosign's verification for |
| 106 | +referrer-stored attestations expects the protobuf bundle, so a |
| 107 | +referrers-plus-DSSE attestation cannot be reliably verified with |
| 108 | +`cosign verify-attestation`. Pairing referrers with protobuf keeps Chains in |
| 109 | +step with cosign and avoids a matrix of combinations that no tool can verify. |
| 110 | + |
| 111 | +Image **signatures** are handled differently from attestations. A cosign image |
| 112 | +signature is a plain signature over the payload, not a DSSE envelope, so in |
| 113 | +referrers mode Chains writes it using cosign's native signature manifest — the |
| 114 | +exact shape `cosign verify` looks for — rather than a protobuf bundle. Signature |
| 115 | +verification therefore works with no extra flags. |
| 116 | + |
| 117 | +## Verifying |
| 118 | + |
| 119 | +Verification is the same in both modes — point cosign at your key: |
| 120 | + |
| 121 | +```shell |
| 122 | +# Verify a signature |
| 123 | +cosign verify \ |
| 124 | + --key k8s://tekton-chains/signing-secrets \ |
| 125 | + <image>@sha256:<digest> |
| 126 | +
|
| 127 | +# Verify an attestation |
| 128 | +cosign verify-attestation \ |
| 129 | + --key k8s://tekton-chains/signing-secrets \ |
| 130 | + --type slsaprovenance \ |
| 131 | + <image>@sha256:<digest> |
| 132 | +``` |
| 133 | + |
| 134 | +> [!IMPORTANT] |
| 135 | +> Starting with **cosign v2.0**, and continuing through the v3.x series, |
| 136 | +> `cosign verify` and `cosign verify-attestation` check for transparency-log |
| 137 | +> (Rekor) inclusion **by default**. Whether that check can pass depends on |
| 138 | +> Chains' transparency setting: |
| 139 | +> |
| 140 | +> - **Transparency disabled** (`transparency.enabled: "false"`): Chains does not |
| 141 | +> record signatures in a transparency log, so there are no Rekor entries and |
| 142 | +> the default cosign check fails with an error such as: |
| 143 | +> |
| 144 | +> ```text |
| 145 | +> Error: no matching signatures: ... not enough verified log entries from |
| 146 | +> transparency log: 0 < 1 |
| 147 | +> ``` |
| 148 | +> |
| 149 | +> This is not a signature problem. Add `--insecure-ignore-tlog=true` to the |
| 150 | +> commands above to verify against the public key alone. |
| 151 | +> |
| 152 | +> - **Transparency enabled** (`transparency.enabled: "true"`): signatures are |
| 153 | +> recorded in Rekor, so the default tlog check passes and no extra flag is |
| 154 | +> needed. |
| 155 | + |
| 156 | +To see what was stored, use [`oras`](https://oras.land/): |
| 157 | + |
| 158 | +```shell |
| 159 | +oras discover <image>@sha256:<digest> |
| 160 | +``` |
| 161 | + |
| 162 | +In `referrers-api` mode you will see two referrers. The signature manifest does not |
| 163 | +have an `artifactType` set (cosign uses the layer `mediaType` instead), so `oras` |
| 164 | +displays it as `<unknown>`. The attestation manifest carries |
| 165 | +`application/vnd.dev.sigstore.bundle.v0.3+json` as its `artifactType`, which `oras` |
| 166 | +displays directly: |
| 167 | + |
| 168 | +```text |
| 169 | +<image>@sha256:<digest> |
| 170 | +├── <unknown> ← signature |
| 171 | +│ └── sha256:<sig-manifest-digest> |
| 172 | +└── application/vnd.dev.sigstore.bundle.v0.3+json ← attestation |
| 173 | + └── sha256:<att-manifest-digest> |
| 174 | +``` |
| 175 | + |
| 176 | +In `legacy` mode, `oras discover` returns no referrers because the signature and |
| 177 | +attestation are stored as ordinary tags (`.sig` / `.att`) rather than referrer |
| 178 | +manifests. |
| 179 | + |
| 180 | +## Things to keep in mind in referrers mode |
| 181 | + |
| 182 | +These are interoperability notes, not bugs in Chains. |
| 183 | + |
| 184 | +1. **`storage.oci.repository` is ignored.** This setting normally redirects where |
| 185 | + OCI signatures and attestations are stored, letting you keep them in a |
| 186 | + different repository from the image. A referrer, by contrast, must live in the |
| 187 | + same repository as the image it points at, because the referrer manifest |
| 188 | + references its subject by digest within that repository. In referrers mode |
| 189 | + Chains logs a warning and stores the referrer next to the image. The override |
| 190 | + still works in `legacy` mode. |
| 191 | + |
| 192 | +2. **Older cosign discovery paths may not surface the attestation.** Chains |
| 193 | + stores attestations as a protobuf bundle, which is the default for current |
| 194 | + cosign versions. Older cosign releases that default to the tag-based layout |
| 195 | + discover attestations by a different type and may not list it. The attestation |
| 196 | + is still present — `oras discover` shows it and policy engines can consume it — |
| 197 | + and `cosign verify` of the signature is unaffected. |
| 198 | + |
| 199 | +3. **Some registries accept a write but don't return it on read.** If a registry |
| 200 | + reports success but you can't read the referrer back, it isn't fully OCI 1.1 |
| 201 | + compliant. Switch that registry to `legacy`. |
| 202 | + |
| 203 | +4. **`oras discover` shows the signature as `<unknown>`.** cosign does not set |
| 204 | + `artifactType` on signature manifests — it identifies signatures by the layer |
| 205 | + `mediaType` (`application/vnd.dev.cosign.simplesigning.v1+json`) instead. As a |
| 206 | + result, `oras` cannot identify the artifact type and falls back to `<unknown>`. |
| 207 | + This is expected and consistent across all registries. The attestation always |
| 208 | + shows its `artifactType` (`application/vnd.dev.sigstore.bundle.v0.3+json`) |
| 209 | + because that is set on the manifest itself. Verification with `cosign verify` |
| 210 | + and `cosign verify-attestation` is unaffected. |
| 211 | + |
| 212 | +5. **Concurrent writes can race on the tag-schema fallback.** On registries |
| 213 | + without a native Referrers API, the index tag is updated with a |
| 214 | + read-append-write cycle, so simultaneous writes to the same image can drop an |
| 215 | + entry. Registries with the native Referrers API are not affected. This does |
| 216 | + not apply to `legacy` mode. |
| 217 | + |
| 218 | +## Registry compatibility |
| 219 | + |
| 220 | +cosign works with a wide range of registries, including Amazon ECR, Azure |
| 221 | +Container Registry, Docker Hub, GitHub Container Registry, GitLab Container |
| 222 | +Registry, Google Artifact Registry, Harbor, JFrog Artifactory, and Quay. See the |
| 223 | +[cosign registry support page](https://docs.sigstore.dev/cosign/system_config/registry_support/) |
| 224 | +for the current list. |
| 225 | + |
| 226 | +Both `legacy` and `referrers-api` work against any OCI-compliant registry. |
| 227 | +In `referrers-api` mode, registries with a native Referrers API use it directly; |
| 228 | +the rest fall back automatically to the referrers tag schema, as described above. |
| 229 | +You do not need to know a registry's level of OCI 1.1 support in advance. |
| 230 | + |
| 231 | +## See also |
| 232 | + |
| 233 | +- [Chains configuration reference](config.md) — all `storage.oci.*` keys. |
| 234 | +- [Signing](signing.md) — how signing keys and secrets are configured. |
| 235 | +- [cosign registry support](https://docs.sigstore.dev/cosign/system_config/registry_support/) |
| 236 | +- [OCI distribution spec — Listing Referrers](https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#listing-referrers) |
0 commit comments