Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ Supported keys include:
| `storage.gcs.bucket` | The GCS bucket for storage | | |
| `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. | |
| `storage.oci.repository.insecure` | Whether to use insecure connection when connecting to the OCI repository | `true`, `false` | `false` |
| `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` |
| `storage.docdb.url` | The go-cloud URI reference to a docstore collection | `firestore://projects/[PROJECT]/databases/(default)/documents/[COLLECTION]?name_field=name` | |
| `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]` | |
| `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` | |
Expand All @@ -90,6 +91,8 @@ Supported keys include:
>
> **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).

For a full description of each format and registry compatibility see [OCI Artifact Distribution (Referrers)](oci-artifact-distribution-format-referrers-schema.md).

#### docstore

You can read about the go-cloud docstore URI format [here](https://gocloud.dev/howto/docstore/). Tekton Chains supports the following docstore services:
Expand Down
179 changes: 179 additions & 0 deletions docs/oci-artifact-distribution-format-referrers-schema.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
<!--
---
linkTitle: "OCI Artifact Distribution (Referrers)"
weight: 35
---
-->

# OCI Artifact Distribution: the Referrers Schema

When Chains signs an image, it has to put the signature and the attestation
somewhere. Both are stored in the same registry as the image. This page explains
the two ways Chains can do that, and how to turn on the newer one.

If you don't change anything, Chains uses the older tag-based layout and just
works. Read on if you want to use the OCI 1.1 Referrers API instead.

## The problem with the old layout

cosign, which Chains uses under the hood, has always stored a signature by
pushing an extra tag next to the image. For an image at digest `sha256:abc...`,
it creates tags like `sha256-abc....sig` and `sha256-abc....att`.

This works on every registry, but it isn't ideal:

- The registry fills up with extra tags that aren't real images.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: This is only an issue if you expect everything to be a pullable container image. If you expect that the registry is an OCI registry, it isn't necessarily an issue. But even with that, it muddies the purpose of a content that is stored in a registry and is referenceable by tags. The supporting metadata can get confused with the top level artifacts themselves. Maybe that is what you were saying below?

- These tags are easy to confuse with actual image tags.
- No OCI standard describes this layout, so every tool has to special-case it.

## What the Referrers schema is

The [OCI 1.1 distribution spec](https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#listing-referrers)
added a standard way to attach one artifact to another. Instead of inventing a
tag, you push the signature or attestation as its own manifest that records the
image it belongs to in a `subject` field. The registry can then answer a simple
question: "what artifacts refer to this image?"

This is the **Referrers schema**. The payoff is no extra tags, a clean registry,
and a standard that registries and policy tools already understand.

## How cosign enables it

cosign (and the `go-containerregistry` library it uses) already speaks the
Referrers schema. If the registry supports the Referrers API natively, cosign
uses it. If it doesn't, cosign falls back to the spec's **referrers tag schema**:
Comment on lines +43 to +44

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't necessarily the case. I don't think cosign would have used the referrer's API in v2 unless an additional option was passed.

But maybe you have tested things more than me, so your content might be more authoritative.

it keeps a single `sha256-<digest>` index tag and uses it to track referrers.

Either way it is still "referrers mode" — the content is the same, no `.sig` or
`.att` tags are created, and `cosign verify` and `oras discover` both work. This

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another issue is that the .att points to a manifest referencing all of the attestations. This does not have a stable digest. If some other process pushes additional attestations, I think they will all be added into this one manifest.

By moving attestations to use the referrer's API, each attestation will have its own image manifest and will therefore have a stable digest.

This was one of the primary motivators why I wanted to change.

fallback is automatic and needs no configuration, so it covers registries that
don't yet have native support.

## How Chains enables it

Chains exposes this through a single config flag in the `chains-config`
ConfigMap:

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: chains-config
namespace: tekton-chains
data:
storage.oci.distribution-method: "referrers-api"
```

| Value | What Chains does |
|---|---|
| `legacy` (default) | Old tag-based layout (`.sig` / `.att` tags), DSSE-encoded. Works everywhere. |
| `referrers-api` | OCI 1.1 Referrers schema. No extra tags, with automatic fallback on registries that lack native support. |

That's the only knob. You pick *where* artifacts go, and Chains picks the right
encoding to match (explained next).

> [!NOTE]
> This flag only takes effect when the OCI storage backend is in use — that is,
> when `artifacts.oci.storage`, `artifacts.taskrun.storage`, or
> `artifacts.pipelinerun.storage` includes `oci`. If you store signatures and
> attestations somewhere else (for example Tekton results, a docstore, or
> Grafeas), `storage.oci.distribution-method` has no effect.

> [!TIP]
> The cosign bundled with Chains is new enough for everything described here, so
> you don't need to install anything or pass extra flags.
Comment on lines +82 to +84

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems out of context?


## Why referrers + protobuf, and not referrers + DSSE

There is one subtlety worth understanding: the *encoding* of the payload.

- In **legacy** mode, the attestation is a **DSSE** envelope. This is what cosign
has always written and what existing tooling verifies.
- In **referrers** mode, Chains writes the attestation as a **Sigstore protobuf
bundle**.
Comment on lines +90 to +93

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you want to differentiate by what is the default configuration for cosign versions?


You might expect a third option — referrers with DSSE — but it's a dead end in
practice. cosign's verification for referrer-stored attestations expects the
protobuf bundle, not a DSSE envelope. A DSSE-over-referrers attestation can be
written, but `cosign verify-attestation` won't reliably verify it. The industry
is also moving toward the protobuf bundle as the standard format.

So Chains keeps it simple: legacy means DSSE, referrers means protobuf. The two
are tied together on purpose — it avoids a confusing matrix of combinations,
some of which no tool can actually verify. If a genuine need for a separate
encoding option ever appears, it can be added later without changing this flag.
Comment on lines +95 to +104

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just simplify this to state that we maintain support with cosign which doesn't support referrer's without protobuf?


Image **signatures** are not affected by this. In referrers mode they use
cosign's native signature manifest — the exact thing `cosign verify` looks for —
so signature verification works with no extra flags.
Comment on lines +106 to +108

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought the referring signatures were stored as protobuf as well.


## Verifying

Verification is the same in both modes — point cosign at your key:

```shell
# Verify a signature
cosign verify \
--key k8s://tekton-chains/signing-secrets \
<image>@sha256:<digest>

# Verify an attestation
cosign verify-attestation \
--key k8s://tekton-chains/signing-secrets \
--type slsaprovenance \
<image>@sha256:<digest>
```

To see what was stored, use [`oras`](https://oras.land/):

```shell
oras discover <image>@sha256:<digest>
```

## Things to keep in mind in referrers mode

These are interoperability notes, not bugs in Chains.

1. **`storage.oci.repository` is ignored.** A referrer has to live next to the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this used today?

image it points at, so the override that redirects storage elsewhere doesn't
apply. Chains logs a warning and stores the referrer next to the image. The
override still works in `legacy` mode.

2. **cosign's `--experimental-oci11` discovery may not find the attestation.**
Chains stores it as a protobuf bundle; that older discovery path filters on a
different type. The attestation is still there — `oras discover` shows it and
policy engines can use it — and `cosign verify` of the signature is
unaffected.
Comment on lines +142 to +146

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there cosign documentation that we can point to? I don't think that this option exists for all versions of cosign (i.e. protobuf is the default for newer cosign versions, so I wouldn't expect it to be there).


3. **Some registries accept a write but don't return it on read.** If a registry
reports success but you can't read the referrer back, it isn't fully OCI 1.1
compliant. Switch that registry to `legacy`.

4. **`oras discover` may show a different `artifactType` per registry.** This is
display only; the stored content and verification don't change.

5. **Concurrent writes can race on fallback registries.** Without the native API,
the index tag is updated with read-append-write, so simultaneous writes to the
same image can drop an entry. A registry with the native Referrers API avoids
this.
Comment on lines +155 to +158

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This refers just to the legacy mode?


## Registry compatibility

cosign works with a wide range of registries, including AWS ECR, GCP Artifact
Registry, Docker Hub, Azure Container Registry, JFrog Artifactory, GitLab and
GitHub Container Registries, Harbor, and Quay. See the
Comment on lines +162 to +164

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alphabetize?

[cosign registry support page](https://docs.sigstore.dev/cosign/system_config/registry_support/)
for the current list.

| Registry | `legacy` | `referrers-api` |
|---|---|---|
| GCR, ECR, Artifact Registry, quay.io | ✓ | ✓ (native Referrers API) |
| GHCR (`ghcr.io`) | ✓ | ✓ (native API not exposed; cosign uses the tag-schema fallback) |
| Any other OCI registry | ✓ | ✓ (native API if available, otherwise tag-schema fallback) |
Comment on lines +168 to +172

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This table is confusing. I was expecting that it was trying to say which ones had support for the actual referrer's API but you are just saying waht is compatible?

I feel like this shouldn't be included so that you are not expected to keep it up to daet.


## See also

- [Chains configuration reference](config.md) — all `storage.oci.*` keys.
- [Signing](signing.md) — how signing keys and secrets are configured.
- [cosign registry support](https://docs.sigstore.dev/cosign/system_config/registry_support/)
- [OCI distribution spec — Listing Referrers](https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#listing-referrers)
11 changes: 11 additions & 0 deletions pkg/chains/signing.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,16 @@ func (o *ObjectSigner) Sign(ctx context.Context, tektonObj objects.TektonObject)
}
measureMetrics(ctx, metrics.SignedMessagesCount, o.Recorder)

// Attempt to extract the public key so storage backends that need it
// (e.g. protobuf-bundle OCI format) can use it without re-fetching.
// This is intentionally non-fatal: for the default legacy format the
// key is never used, so a transient KMS error here must not prevent
// signatures from being stored.
pubKey, pubKeyErr := signer.PublicKey()
if pubKeyErr != nil {
logger.Warnf("Could not extract public key from signer (will be unavailable to storage backends): %v", pubKeyErr)
}

// Now store those!
for _, backend := range sets.List[string](signableType.StorageBackend(cfg)) {
b, ok := o.Backends[backend]
Expand All @@ -202,6 +212,7 @@ func (o *ObjectSigner) Sign(ctx context.Context, tektonObj objects.TektonObject)
FullKey: signableType.FullKey(obj),
Cert: signer.Cert(),
Chain: signer.Chain(),
PublicKey: pubKey,
PayloadFormat: payloadFormat,
}
if err := b.StorePayload(ctx, tektonObj, rawPayload, string(signature), storageOpts); err != nil {
Expand Down
6 changes: 6 additions & 0 deletions pkg/chains/signing/iface.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ limitations under the License.
package signing

import (
"crypto"

"github.com/sigstore/sigstore/pkg/signature"
)

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