Text Blame Raw

Signature access protocols

The github.com/containers/image library supports signatures implemented as blobs “attached to” an image. Some image transports (local storage formats and remote procotocols) implement these signatures natively or trivially; for others, the protocol extensions described below are necessary.

docker/distribution registries—separate storage

Usage

Any existing docker/distribution registry, whether or not it natively supports signatures, can be augmented with separate signature storage by configuring a signature storage URL in registries.d. registries.d can be configured to use one storage URL for a whole docker/distribution server, or also separate URLs for smaller namespaces or individual repositories within the server (which e.g. allows image authors to manage their own signature storage while publishing the images on the public docker.io server).

The signature storage URL defines a root of a path hierarchy. It can be either a file:///… URL, pointing to a local directory structure, or a http/https URL, pointing to a remote server. file:/// signature storage can be both read and written, http/https only supports reading.

The same path hierarchy is used in both cases, so the HTTP/HTTPS server can be a simple static web server serving a directory structure created by writing to a file:/// signature storage. (This of course does not prevent other server implementations, e.g. a HTTP server reading signatures from a database.)

The usual workflow for producing and distributing images using the separate storage mechanism is to configure the repository in registries.d with sigstore-staging URL pointing to a private file:/// staging area, and a sigstore URL pointing to a public web server. To publish an image, the image author would sign the image as necessary (e.g. using skopeo copy), and then copy the created directory structure from the file:/// staging area to a subdirectory of a webroot of the public web server so that they are accessible using the public sigstore URL. The author would also instruct consumers of the image to, or provide a registries.d configuration file to, set up a sigstore URL pointing to the public web server.

Path structure

Given a base signature storage URL configured in registries.d as mentioned above, and a container image stored in a docker/distribution registry using the fully-expanded name hostname/namespaces/name{@digest,:tag} (e.g. for docker.io/library/busybox:latest, namespaces is library, even if the user refers to the image using the shorter syntax as busybox:latest), signatures are accessed using URLs of the form

base/namespaces/name@digest-algo=digest-value/signature-index

where digest-algo:digest-value is a manifest digest usable for referencing the relevant image manifest (i.e. even if the user referenced the image using a tag, the signature storage is always disambiguated using digest references). Note that in the URLs used for signatures, digest-algo and digest-value are separated using the = character, not : like when acessing the manifest using the docker/distribution API.

Within the URL, index is a decimal integer (in the canonical form), starting with 1. Signatures are stored at URLs with successive index values; to read all of them, start with index=1, and continue reading signatures and increasing index as long as signatures with these index values exist. Similarly, to add one more signature to an image, find the first index which does not exist, and then store the new signature using that index value.

There is no way to list existing signatures other than iterating through the successive index values, and no way to download all of the signatures at once.

Examples

For a docker/distribution image available as busybox@sha256:817a12c32a39bbe394944ba49de563e085f1d3c5266eb8e9723256bc4448680e (or as busybox:latest if the latest tag points to to a manifest with the same digest), and with a registries.d configuration specifying a sigstore URL https://example.com/sigstore for the same image, the following URLs would be accessed to download all signatures:

  • https://example.com/sigstore/library/busybox@sha256=817a12c32a39bbe394944ba49de563e085f1d3c5266eb8e9723256bc4448680e/signature-1
  • https://example.com/sigstore/library/busybox@sha256=817a12c32a39bbe394944ba49de563e085f1d3c5266eb8e9723256bc4448680e/signature-2

For a docker/distribution image available as example.com/ns1/ns2/ns3/repo@somedigest:digestvalue and the same sigstore URL, the signatures would be available at

https://example.com/sigstore/ns1/ns2/ns3/repo@somedigest=digestvalue/signature-1

and so on.

(OpenShift) docker/distribution API extension

As of https://github.com/openshift/origin/pull/12504/ , the OpenShift-embedded registry also provides an extension of the docker/distribution API which allows simpler access to the signatures, using only the docker/distribution API endpoint.

This API is not inherently OpenShift-specific (e.g. the client does not need to know the OpenShift API endpoint, and credentials sufficient to access the docker/distribution API server are sufficient to access signatures as well), and it is the preferred way implement signature storage in registries.

See https://github.com/openshift/openshift-docs/pull/3556 for the upstream documentation of the API.

To read the signature, any user with access to an image can use the /extensions/v2/…/signatures/… path to read an array of signatures. Use only the signature objects which have version equal to 2, type equal to atomic, and read the signature from content; ignore the other fields of the signature object.

To add a single signature, PUT a new object with version set to 2, type set to atomic, and content set to the signature. Also set name to an unique name with the form digest@per-image-name, where digest is an image manifest digest (also used in the URL), and per-image-name is any unique identifier.

To add more than one signature, add them one at a time. This API does not allow deleting signatures.

Note that because signatures are stored within the cluster-wide image objects, i.e. different namespaces can not associate different sets of signatures to the same image, updating signatures requires a cluster-wide access to the imagesignatures resource (by default available to the system:image-signer role),

OpenShift-embedded registries

The OpenShift-embedded registry implements the ordinary docker/distribution API, and it also exposes images through the OpenShift REST API (available through the “API master” servers).

Note: OpenShift versions 1.5 and later support the above-described docker/distribution API extension, which is easier to set up and should usually be preferred. Continue reading for details on using older versions of OpenShift.

As of https://github.com/openshift/origin/pull/9181, signatures are exposed through the OpenShift API (i.e. to access the complete image, it is necessary to use both APIs, in particular to know the URLs for both the docker/distribution and the OpenShift API master endpoints).

To read the signature, any user with access to an image can use the imagestreamimages namespaced resource to read an Image object and its Signatures array. Use only the ImageSignature objects which have Type equal to atomic, and read the signature from Content; ignore the other fields of the ImageSignature object.

To add or remove signatures, use the cluster-wide (non-namespaced) imagesignatures resource, with Type set to atomic and Content set to the signature. Signature names must have the form digest@per-image-name, where digest is an image manifest digest (OpenShift “image name”), and per-image-name is any unique identifier.

Note that because signatures are stored within the cluster-wide image objects, i.e. different namespaces can not associate different sets of signatures to the same image, updating signatures requires a cluster-wide access to the imagesignatures resource (by default available to the system:image-signer role), and deleting signatures is strongly discouraged (it deletes the signature from all namespaces which contain the same image).