Skip to content

Commit ee93f82

Browse files
feat: add OCI 1.1 Referrers API support with configurable distribution and serialization
Signed-off-by: Anitha Natarajan <anataraj@redhat.com>
1 parent 2a68e6a commit ee93f82

14 files changed

Lines changed: 1104 additions & 39 deletions

docs/config.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ 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. See [OCI Storage Formats](oci-storage-formats.md) for details. | `legacy`, `referrers-api` | `legacy` |
73+
| `storage.oci.serialization-format` | Controls how the cryptographic payload is encoded. `protobuf-bundle` requires `storage.oci.distribution-method: referrers-api`. See [OCI Storage Formats](oci-storage-formats.md) for details. | `dsse`, `protobuf-bundle` | `dsse` |
7274
| `storage.docdb.url` | The go-cloud URI reference to a docstore collection | `firestore://projects/[PROJECT]/databases/(default)/documents/[COLLECTION]?name_field=name` | |
7375
| `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]` | |
7476
| `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 +92,8 @@ Supported keys include:
9092
>
9193
> **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).
9294
95+
For a full description of each format, registry compatibility, and migration guidance see [OCI Storage Formats](oci-storage-formats.md).
96+
9397
#### docstore
9498

9599
You can read about the go-cloud docstore URI format [here](https://gocloud.dev/howto/docstore/). Tekton Chains supports the following docstore services:

docs/oci-storage-formats.md

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
<!--
2+
---
3+
linkTitle: "OCI Storage Formats"
4+
weight: 35
5+
---
6+
-->
7+
8+
# OCI Storage Formats
9+
10+
## Overview
11+
12+
By default, Chains stores OCI signatures and attestations using a tag-based
13+
scheme (e.g. `<image>:sha256-<digest>.sig`). While universally supported, this
14+
approach creates additional tags in your registry for every signed artefact.
15+
16+
Two independent configuration keys let you control this behaviour:
17+
18+
- **`storage.oci.distribution-method`***where* the artefacts are stored in the registry.
19+
- **`storage.oci.serialization-format`***how* the cryptographic payload is encoded.
20+
21+
## Configuration
22+
23+
Set the keys in the `chains-config` ConfigMap:
24+
25+
```yaml
26+
apiVersion: v1
27+
kind: ConfigMap
28+
metadata:
29+
name: chains-config
30+
namespace: tekton-chains
31+
data:
32+
storage.oci.distribution-method: "referrers-api"
33+
storage.oci.serialization-format: "dsse"
34+
```
35+
36+
### `storage.oci.distribution-method`
37+
38+
Controls where signatures and attestations are attached to the subject image.
39+
40+
| Value | Storage mechanism | Default |
41+
|---|---|---|
42+
| `legacy` | Tag-based (`sha256-<digest>.sig` / `.att`) | ✓ |
43+
| `referrers-api` | OCI 1.1 Referrers API (no extra tags) | |
44+
45+
### `storage.oci.serialization-format`
46+
47+
Controls how the cryptographic payload is encoded.
48+
49+
| Value | Encoding | Default |
50+
|---|---|---|
51+
| `dsse` | [Dead Simple Signing Envelope](https://github.com/secure-systems-lab/dsse) | ✓ |
52+
| `protobuf-bundle` | [Sigstore protobuf bundle](https://github.com/sigstore/protobuf-specs) (experimental) | |
53+
54+
> [!NOTE]
55+
> `protobuf-bundle` requires `distribution-method: referrers-api`.
56+
> Combining `legacy` with `protobuf-bundle` is rejected at startup.
57+
58+
### Valid combinations
59+
60+
| `distribution-method` | `serialization-format` | Description |
61+
|---|---|---|
62+
| `legacy` | `dsse` | **Default.** Tag-based storage, DSSE encoding. Maximum compatibility. |
63+
| `referrers-api` | `dsse` | OCI 1.1 Referrers API, DSSE encoding. Reduces tag proliferation. |
64+
| `referrers-api` | `protobuf-bundle` | OCI 1.1 Referrers API, Sigstore bundle. Experimental. |
65+
66+
## Formats in detail
67+
68+
### `legacy` distribution + `dsse` serialization (default)
69+
70+
Chains pushes signatures and attestations to tags derived from the image digest:
71+
72+
- `<image>:sha256-<digest>.sig` – signature
73+
- `<image>:sha256-<digest>.att` – attestation
74+
75+
Works with every OCI registry and every version of cosign.
76+
77+
**Use when:** you need maximum compatibility with existing tooling or registries
78+
that do not support the OCI 1.1 distribution spec.
79+
80+
### `referrers-api` distribution + `dsse` serialization
81+
82+
Chains uses the [OCI 1.1 Referrers API](https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#listing-referrers)
83+
to associate signatures and attestations with the subject image. No extra tags
84+
are created.
85+
86+
The payload encoding is identical to `legacy` (DSSE), so any tooling that can
87+
verify DSSE-wrapped attestations continues to work.
88+
89+
If the registry does not support the Referrers API, cosign automatically falls
90+
back to the [referrers tag schema](https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#referrers-tag-schema)
91+
as required by the OCI distribution spec.
92+
93+
**Use when:** you want to reduce tag proliferation and your registry supports
94+
OCI 1.1 (e.g. quay.io, GHCR, GCR, ECR).
95+
96+
### `referrers-api` distribution + `protobuf-bundle` serialization (experimental)
97+
98+
Chains creates a [Sigstore protobuf bundle](https://github.com/sigstore/protobuf-specs)
99+
and pushes it via the OCI 1.1 Referrers API.
100+
101+
> [!WARNING]
102+
> This format is **experimental**. It requires cosign ≥ 2.4 for verification
103+
> and the bundle structure may change in future Chains or cosign releases.
104+
105+
**Use when:** you are testing the new Sigstore bundle format or need
106+
interoperability with tools that consume protobuf bundles.
107+
108+
## Registry compatibility
109+
110+
| Registry | `legacy` | `referrers-api` |
111+
|---|---|---|
112+
| Any OCI registry | ✓ | ✓ (fallback to referrers tag schema if native API unavailable) |
113+
| GHCR (`ghcr.io`) | ✓ | ✓ — native Referrers API not exposed; cosign falls back to the OCI referrers tag schema automatically. Referrers are stored and discoverable via `oras discover --distribution-spec v1.1-referrers-tag`. |
114+
| GCR, ECR, Artifact Registry | ✓ | ✓ (native Referrers API) |
115+
| quay.io | ✓ | ⚠️ — write is accepted (HTTP 200) but referrers are not discoverable via either the native API or the tag schema fallback. This is a known quay.io registry limitation. Use `legacy` mode with quay.io until full OCI 1.1 support is available. |
116+
117+
> [!NOTE]
118+
> Registry support for OCI 1.1 referrers is still rolling out across the ecosystem.
119+
> If `oras discover --distribution-spec v1.1-referrers-api` returns `unsupported`,
120+
> cosign will automatically fall back to the referrers tag schema
121+
> (`oras discover --distribution-spec v1.1-referrers-tag`) as required by the OCI
122+
> distribution spec. In either case no `.sig`/`.att` tags are created.
123+
124+
## Verifying stored artefacts
125+
126+
Regardless of the configuration used, cosign can verify both signatures and
127+
attestations. Point cosign at your signing key or certificate and run:
128+
129+
```shell
130+
# Verify a signature
131+
cosign verify \
132+
--key k8s://tekton-chains/signing-secrets \
133+
<image>@sha256:<digest>
134+
135+
# Verify an in-toto attestation
136+
cosign verify-attestation \
137+
--key k8s://tekton-chains/signing-secrets \
138+
--type slsaprovenance \
139+
<image>@sha256:<digest>
140+
```
141+
142+
In `referrers-api` mode, signatures are written using cosign's native signature
143+
manifest (`config.mediaType: application/vnd.dev.cosign.artifact.sig.v1+json`
144+
with the image set as the manifest `subject`). This is the exact shape `cosign
145+
verify` discovers, so verification works with **no extra flags** on registries
146+
that support the Referrers API — the same command shown above works for both
147+
`legacy` and `referrers-api` distribution methods.
148+

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)