Skip to content
Open
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.encoding-format` | Controls the payload encoding for OCI artifact storage and implicitly the storage layout: `dsse` (default) uses DSSE-encoded payloads stored under .sig/.att tags, `sigstore-bundle` uses the Sigstore protobuf-bundle format stored via the OCI 1.1 Referrers API. See [OCI Storage Encoding Format](oci-encoding-format.md) for details. | `dsse`, `sigstore-bundle` | `dsse` |
| `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 Storage Encoding Format](oci-encoding-format.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
246 changes: 246 additions & 0 deletions docs/oci-encoding-format.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
<!--
---
linkTitle: "OCI Encoding Format"
weight: 35
---
-->

# OCI Storage Encoding Format

The `storage.oci.encoding-format` key controls how Chains serializes OCI
signatures and attestations when using the OCI storage backend. Setting this
key also determines the storage layout, because the two are tightly coupled —
see [Why they are coupled](#why-the-encoding-and-layout-are-coupled).

If you don't change anything, Chains keeps the existing `dsse` behavior and
everything just works. Read on if you want to opt into `sigstore-bundle`, or to
understand the difference.

## Encoding formats

The *encoding* is the serialization format of the attestation payload itself.

### DSSE (default)

[Dead Simple Signing Envelope](https://github.com/secure-systems-lab/dsse) is
the format cosign has used since its early versions. An attestation is a JSON
object with a base64-encoded payload and one or more signatures computed over
the DSSE Pre-Authentication Encoding (PAE) of the content.

DSSE is widely supported and is what all existing tooling — older
`cosign verify-attestation`, most policy engines — expects by default.

### Sigstore protobuf bundle

The [Sigstore bundle specification](https://github.com/sigstore/protobuf-specs)
defines a Protocol Buffer message that combines the payload, signature, signing
certificate, and optional transparency log entry into a single self-contained
artifact. This is the default format for cosign v3+ (it was available in
v2.4–v2.6 behind the `--new-bundle-format` flag).

Because the bundle includes the certificate and tlog entry inline, it supports
offline verification without out-of-band trust anchor lookups.

## How the encoding determines the storage layout

The encoding choice also determines *where* signatures and attestations are
stored in the registry. The supported combinations are:

| `encoding-format` | Attestation encoding | Storage layout |
|---|---|---|
| `dsse` (default) | DSSE JSON envelope | Tag-based: `.sig` / `.att` tags pushed alongside the image |
| `sigstore-bundle` | Sigstore protobuf bundle | OCI 1.1 Referrers API: referrer manifests with a `subject` field |

### Tag-based layout (`dsse`)

cosign stores signatures and attestations as extra tags next to the image:
`sha256-<digest>.sig` and `sha256-<digest>.att`. Every OCI-compliant registry
supports this, but the tags have drawbacks:

- Tags are meant to name top-level artifacts. Using them for signatures and
attestations puts metadata in the same namespace as real images.
- The `.att` tag holds *all* attestations in a single manifest whose digest
changes with each write, so there is no stable, individually addressable
reference to any single attestation.
- No OCI standard describes this layout, so every tool must special-case it.

### OCI 1.1 Referrers API (`sigstore-bundle`)

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. Each signature or
attestation is pushed as its own manifest with a `subject` field pointing to
the image it belongs to. No extra tags are created, and any OCI-compliant tool
can discover the relationship via the Referrers API.

> [!NOTE]
> On registries without native Referrers API support,
> `go-containerregistry` (the library Chains uses) automatically falls back to
> the spec's referrers tag schema — a single `sha256-<digest>` index tag
> listing all referrers. The protobuf bundle encoding is unchanged; only the
> discovery mechanism differs. No configuration is needed.

## Why the encoding and layout are coupled

The encoding and storage layout are intentionally coupled into a single knob.
`dsse` always uses tag-based storage; `sigstore-bundle` always uses the OCI 1.1
Referrers API. There is no mix-and-match.

Image **signatures** are handled separately from attestations. A cosign image
signature is a plain signature over the payload — not a DSSE envelope — so in
`sigstore-bundle` mode Chains writes it using cosign's native signature
manifest (the exact shape `cosign verify` expects) rather than a protobuf
bundle. Signature verification therefore works with no extra flags.

## Configuring the encoding format

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.encoding-format: "sigstore-bundle"
```

| Value | Encoding | Storage layout |
|---|---|---|
| `dsse` (default) | DSSE JSON envelope | `.sig` / `.att` tags |
| `sigstore-bundle` | Sigstore protobuf bundle | OCI 1.1 Referrers API (with automatic fallback to referrers tag schema on older registries) |

> [!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 (docstore, mongo, Grafeas, or other supported
> backends), `storage.oci.encoding-format` has no effect.

> [!TIP]
> Chains vendors the Sigstore libraries it needs, so `sigstore-bundle` mode
> works out of the box — there is nothing extra to install. You only need a
> separate `cosign` or `oras` CLI if you want to verify or inspect stored
> artifacts yourself, as shown below.

## 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>
```

> [!IMPORTANT]
> Starting with **cosign v2.0**, and continuing through the v3.x series,
> `cosign verify` and `cosign verify-attestation` check for transparency-log
> (Rekor) inclusion **by default**. Whether that check can pass depends on
> Chains' transparency setting:
>
> - **Transparency disabled** (`transparency.enabled: "false"`): Chains does not
> record signatures in a transparency log, so there are no Rekor entries and
> the default cosign check fails with an error such as:
>
> ```text
> Error: no matching signatures: ... not enough verified log entries from
> transparency log: 0 < 1
> ```
>
> This is not a signature problem. Add `--insecure-ignore-tlog=true` to the
> commands above to verify against the public key alone.
>
> - **Transparency enabled** (`transparency.enabled: "true"`): signatures are
> recorded in Rekor, so the default tlog check passes and no extra flag is
> needed.

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

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

In `sigstore-bundle` mode you will see two referrers. The signature manifest does not
have an `artifactType` set (cosign uses the layer `mediaType` instead), so `oras`
displays it as `<unknown>`. The attestation manifest carries
`application/vnd.dev.sigstore.bundle.v0.3+json` as its `artifactType`, which `oras`
displays directly:

```text
<image>@sha256:<digest>
├── <unknown> ← signature
│ └── sha256:<sig-manifest-digest>
└── application/vnd.dev.sigstore.bundle.v0.3+json ← attestation
└── sha256:<att-manifest-digest>
```

In `dsse` mode, `oras discover` returns no referrers because the signature and
attestations are stored as ordinary tags (`.sig` / `.att`) rather than referrer
manifests.

## Things to keep in mind in `sigstore-bundle` mode

These are interoperability notes, not bugs in Chains.

1. **`storage.oci.repository` is ignored.** This setting normally redirects where
OCI signatures and attestations are stored, letting you keep them in a
different repository from the image. A referrer, by contrast, must live in the
same repository as the image it points at, because the referrer manifest
references its subject by digest within that repository. In `sigstore-bundle`
mode Chains logs a warning and stores the referrer next to the image. The
override still works in `dsse` mode.

2. **Older cosign discovery paths may not surface the attestation.** Chains
stores attestations as a protobuf bundle, which is the default for current
cosign versions. Older cosign releases that default to the tag-based layout
discover attestations by a different type and may not list it. The attestation
is still present — `oras discover` shows it and policy engines can consume it —
and `cosign verify` of the signature is unaffected.

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 `dsse`.

4. **`oras discover` shows the signature as `<unknown>`.** cosign does not set
`artifactType` on signature manifests — it identifies signatures by the layer
`mediaType` (`application/vnd.dev.cosign.simplesigning.v1+json`) instead. As a
result, `oras` cannot identify the artifact type and falls back to `<unknown>`.
This is expected and consistent across all registries. The attestation always
shows its `artifactType` (`application/vnd.dev.sigstore.bundle.v0.3+json`)
because that is set on the manifest itself. Verification with `cosign verify`
and `cosign verify-attestation` is unaffected.

5. **Concurrent writes can race on the tag-schema fallback.** On registries
without a native Referrers API, the index tag is updated with a
read-append-write cycle, so simultaneous writes to the same image can drop an
entry. Registries with the native Referrers API are not affected. This does
not apply to `dsse` mode.

## Registry compatibility

cosign works with a wide range of registries, including Amazon ECR, Azure
Container Registry, Docker Hub, GitHub Container Registry, GitLab Container
Registry, Google Artifact Registry, Harbor, JFrog Artifactory, and Quay. See the
[cosign registry support page](https://docs.sigstore.dev/cosign/system_config/registry_support/)
for the current list.

Both `dsse` and `sigstore-bundle` work against any OCI-compliant registry.
In `sigstore-bundle` mode, registries with a native Referrers API use it directly;
the rest fall back automatically to the referrers tag schema, as described above.
You do not need to know a registry's level of OCI 1.1 support in advance.

## 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 @@ -222,6 +222,16 @@ func (o *ObjectSigner) Sign(ctx context.Context, tektonObj objects.TektonObject)
}
}

// 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 @@ -238,6 +248,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,
RekorBundle: rekorBundle,
}
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/cosign/v2/pkg/cosign/bundle"
"github.com/sigstore/sigstore/pkg/signature"
)
Expand Down Expand Up @@ -44,4 +46,8 @@ type Bundle struct {
Chain []byte
// RekorBundle is an optional Rekor transparency log bundle, populated when transparency is enabled.
RekorBundle *bundle.RekorBundle
// 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