Signed-off-by: Antonio Murdaca <runcom@redhat.com>
8.3 KiB
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).