Skip to content

Commit 93d718e

Browse files
feat: add OCI 1.1 Referrers API support with configurable distribution
Signed-off-by: Anitha Natarajan <anataraj@redhat.com> Co-authored-by: Copilot <claude-sonnet@users.noreply.github.com>
1 parent 2a68e6a commit 93d718e

14 files changed

Lines changed: 870 additions & 40 deletions

docs/config.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ Supported keys include:
6969
| `storage.gcs.bucket` | The GCS bucket for storage | | |
7070
| `storage.oci.repository` | The OCI repo to store OCI signatures and attestation in | If left undefined _and_ one of `artifacts.{oci,taskrun}.storage` includes `oci` storage, attestations will be stored alongside the stored OCI artifact itself. ([example on GCP](../images/attestations-in-artifact-registry.png)) Defining this value results in the OCI bundle stored in the designated location _instead of_ alongside the image. See [cosign documentation](https://github.com/sigstore/cosign#specifying-registry) for additional information. | |
7171
| `storage.oci.repository.insecure` | Whether to use insecure connection when connecting to the OCI repository | `true`, `false` | `false` |
72+
| `storage.oci.distribution-method` | Controls how OCI signatures and attestations are attached to images in the registry, and implicitly the payload encoding: `legacy` uses tag-based storage with DSSE payloads, `referrers-api` uses the OCI 1.1 Referrers API with Sigstore protobuf-bundle attestations. See [OCI Artifact Distribution (Referrers)](oci-artifact-distribution-format-referrers-schema.md) for details. | `legacy`, `referrers-api` | `legacy` |
7273
| `storage.docdb.url` | The go-cloud URI reference to a docstore collection | `firestore://projects/[PROJECT]/databases/(default)/documents/[COLLECTION]?name_field=name` | |
7374
| `storage.docdb.mongo-server-url` (optional) | The value of MONGO_SERVER_URL env var with the MongoDB connection URI | Example: `mongodb://[USER]:[PASSWORD]@[HOST]:[PORT]/[DATABASE]` | |
7475
| `storage.docdb.mongo-server-url-dir` (optional) | The path of the directory that contains the file named MONGO_SERVER_URL that stores the value of MONGO_SERVER_URL env var | If the file `/mnt/mongo-creds-secret/MONGO_SERVER_URL` has the value of MONGO_SERVER_URL, then set `storage.docdb.mongo-server-url-dir: /mnt/mongo-creds-secret` | |
@@ -90,6 +91,8 @@ Supported keys include:
9091
>
9192
> **Recommendation**: Only use `storage.oci.repository.insecure: true` in development or test environments. For production deployments, always use secure HTTPS connections with valid TLS certificates (`storage.oci.repository.insecure: false`, which is the default).
9293
94+
For a full description of each format and registry compatibility see [OCI Artifact Distribution (Referrers)](oci-artifact-distribution-format-referrers-schema.md).
95+
9396
#### docstore
9497

9598
You can read about the go-cloud docstore URI format [here](https://gocloud.dev/howto/docstore/). Tekton Chains supports the following docstore services:
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
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 (for example Tekton results, a docstore, or
86+
> Grafeas), `storage.oci.distribution-method` has no effect.
87+
88+
> [!TIP]
89+
> Chains vendors the Sigstore libraries it needs, so referrers mode works out of
90+
> the box — there is nothing extra to install. You only need a separate `cosign`
91+
> or `oras` CLI if you want to verify or inspect the stored artifacts yourself,
92+
> as shown below.
93+
94+
## Why the encoding is tied to the layout
95+
96+
The two choices line up with cosign's own defaults as they have evolved:
97+
98+
- **legacy** matches cosign's original default — tag-based storage with the
99+
attestation written as a **DSSE** envelope. This is what existing tooling has
100+
always verified.
101+
- **referrers-api** writes the attestation over the OCI 1.1 Referrers API as a
102+
**Sigstore protobuf bundle**, the format newer cosign versions write and
103+
verify.
104+
105+
Chains deliberately ties the encoding to the layout instead of exposing it as a
106+
separate switch. The reason is compatibility: cosign's verification for
107+
referrer-stored attestations expects the protobuf bundle, so a
108+
referrers-plus-DSSE attestation cannot be reliably verified with
109+
`cosign verify-attestation`. Pairing referrers with protobuf keeps Chains in
110+
step with cosign and avoids a matrix of combinations that no tool can verify.
111+
112+
Image **signatures** are handled differently from attestations. A cosign image
113+
signature is a plain signature over the payload, not a DSSE envelope, so in
114+
referrers mode Chains writes it using cosign's native signature manifest — the
115+
exact shape `cosign verify` looks for — rather than a protobuf bundle. Signature
116+
verification therefore works with no extra flags.
117+
118+
## Verifying
119+
120+
Verification is the same in both modes — point cosign at your key:
121+
122+
```shell
123+
# Verify a signature
124+
cosign verify \
125+
--key k8s://tekton-chains/signing-secrets \
126+
<image>@sha256:<digest>
127+
128+
# Verify an attestation
129+
cosign verify-attestation \
130+
--key k8s://tekton-chains/signing-secrets \
131+
--type slsaprovenance \
132+
<image>@sha256:<digest>
133+
```
134+
135+
> [!IMPORTANT]
136+
> Starting with **cosign v2.0**, and continuing through the v3.x series,
137+
> `cosign verify` and `cosign verify-attestation` check for transparency-log
138+
> (Rekor) inclusion **by default**. Whether that check can pass depends on
139+
> Chains' transparency setting:
140+
>
141+
> - **Transparency disabled** (`transparency.enabled: "false"`): Chains does not
142+
> record signatures in a transparency log, so there are no Rekor entries and
143+
> the default cosign check fails with an error such as:
144+
>
145+
> ```text
146+
> Error: no matching signatures: ... not enough verified log entries from
147+
> transparency log: 0 < 1
148+
> ```
149+
>
150+
> This is not a signature problem. Add `--insecure-ignore-tlog=true` to the
151+
> commands above to verify against the public key alone.
152+
>
153+
> - **Transparency enabled** (`transparency.enabled: "true"`): signatures are
154+
> recorded in Rekor, so the default tlog check passes and no extra flag is
155+
> needed.
156+
157+
To see what was stored, use [`oras`](https://oras.land/):
158+
159+
```shell
160+
oras discover <image>@sha256:<digest>
161+
```
162+
163+
## Things to keep in mind in referrers mode
164+
165+
These are interoperability notes, not bugs in Chains.
166+
167+
1. **`storage.oci.repository` is ignored.** This setting normally redirects where
168+
OCI signatures and attestations are stored, letting you keep them in a
169+
different repository from the image. A referrer, by contrast, must live in the
170+
same repository as the image it points at, because the referrer manifest
171+
references its subject by digest within that repository. In referrers mode
172+
Chains logs a warning and stores the referrer next to the image. The override
173+
still works in `legacy` mode.
174+
175+
2. **Older cosign discovery paths may not surface the attestation.** Chains
176+
stores attestations as a protobuf bundle, which is the default for current
177+
cosign versions. Older cosign releases that default to the tag-based layout
178+
discover attestations by a different type and may not list it. The attestation
179+
is still present — `oras discover` shows it and policy engines can consume it —
180+
and `cosign verify` of the signature is unaffected.
181+
182+
3. **Some registries accept a write but don't return it on read.** If a registry
183+
reports success but you can't read the referrer back, it isn't fully OCI 1.1
184+
compliant. Switch that registry to `legacy`.
185+
186+
4. **`oras discover` may show a different `artifactType` per registry.** This is
187+
display only; the stored content and verification don't change.
188+
189+
5. **Concurrent writes can race on the tag-schema fallback.** On registries
190+
without a native Referrers API, the index tag is updated with a
191+
read-append-write cycle, so simultaneous writes to the same image can drop an
192+
entry. Registries with the native Referrers API are not affected. This does
193+
not apply to `legacy` mode.
194+
195+
## Registry compatibility
196+
197+
cosign works with a wide range of registries, including Amazon ECR, Azure
198+
Container Registry, Docker Hub, GitHub Container Registry, GitLab Container
199+
Registry, Google Artifact Registry, Harbor, JFrog Artifactory, and Quay. See the
200+
[cosign registry support page](https://docs.sigstore.dev/cosign/system_config/registry_support/)
201+
for the current list.
202+
203+
Both `legacy` and `referrers-api` work against any OCI-compliant registry.
204+
In `referrers-api` mode, registries with a native Referrers API use it directly;
205+
the rest fall back automatically to the referrers tag schema, as described above.
206+
You do not need to know a registry's level of OCI 1.1 support in advance.
207+
208+
## See also
209+
210+
- [Chains configuration reference](config.md) — all `storage.oci.*` keys.
211+
- [Signing](signing.md) — how signing keys and secrets are configured.
212+
- [cosign registry support](https://docs.sigstore.dev/cosign/system_config/registry_support/)
213+
- [OCI distribution spec — Listing Referrers](https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#listing-referrers)

pkg/chains/signing.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,16 @@ func (o *ObjectSigner) Sign(ctx context.Context, tektonObj objects.TektonObject)
186186
}
187187
measureMetrics(ctx, metrics.SignedMessagesCount, o.Recorder)
188188

189+
// Attempt to extract the public key so storage backends that need it
190+
// (e.g. protobuf-bundle OCI format) can use it without re-fetching.
191+
// This is intentionally non-fatal: for the default legacy format the
192+
// key is never used, so a transient KMS error here must not prevent
193+
// signatures from being stored.
194+
pubKey, pubKeyErr := signer.PublicKey()
195+
if pubKeyErr != nil {
196+
logger.Warnf("Could not extract public key from signer (will be unavailable to storage backends): %v", pubKeyErr)
197+
}
198+
189199
// Now store those!
190200
for _, backend := range sets.List[string](signableType.StorageBackend(cfg)) {
191201
b, ok := o.Backends[backend]
@@ -202,6 +212,7 @@ func (o *ObjectSigner) Sign(ctx context.Context, tektonObj objects.TektonObject)
202212
FullKey: signableType.FullKey(obj),
203213
Cert: signer.Cert(),
204214
Chain: signer.Chain(),
215+
PublicKey: pubKey,
205216
PayloadFormat: payloadFormat,
206217
}
207218
if err := b.StorePayload(ctx, tektonObj, rawPayload, string(signature), storageOpts); err != nil {

pkg/chains/signing/iface.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ limitations under the License.
1414
package signing
1515

1616
import (
17+
"crypto"
18+
1719
"github.com/sigstore/sigstore/pkg/signature"
1820
)
1921

@@ -41,4 +43,8 @@ type Bundle struct {
4143
Cert []byte
4244
// Cert is an optional PEM encoded x509 certificate chain, if one was used for signing.
4345
Chain []byte
46+
// PublicKey is the public key from the signer.
47+
// Available for storage backends that need direct access to the key material
48+
// (e.g. to create a cosign protobuf bundle without a certificate).
49+
PublicKey crypto.PublicKey
4450
}

0 commit comments

Comments
 (0)