Skip to content

Commit 4c4b7ff

Browse files
committed
Add documentation for signature access protocols
This is primarily the only documentation of the sigstore layout; in addition it comments on the OpenShift API master REST API and the OpenShift docker/distribution API extension. Signed-off-by: Miloslav Trmač <mitr@redhat.com>
1 parent 3149c1a commit 4c4b7ff

4 files changed

Lines changed: 122 additions & 0 deletions

File tree

docker/docker_image_dest.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ func (d *dockerImageDestination) PutSignatures(signatures [][]byte) error {
245245
return errors.Errorf("Unknown manifest digest, can't add signatures")
246246
}
247247

248+
// NOTE: Keep this in sync with docs/signature-protocols.md!
248249
for i, signature := range signatures {
249250
url := signatureStorageURL(d.c.signatureBase, d.manifestDigest, i)
250251
if url == nil {
@@ -278,6 +279,7 @@ func (d *dockerImageDestination) PutSignatures(signatures [][]byte) error {
278279
}
279280

280281
// putOneSignature stores one signature to url.
282+
// NOTE: Keep this in sync with docs/signature-protocols.md!
281283
func (d *dockerImageDestination) putOneSignature(url *url.URL, signature []byte) error {
282284
switch url.Scheme {
283285
case "file":
@@ -301,6 +303,7 @@ func (d *dockerImageDestination) putOneSignature(url *url.URL, signature []byte)
301303

302304
// deleteOneSignature deletes a signature from url, if it exists.
303305
// If it successfully determines that the signature does not exist, returns (true, nil)
306+
// NOTE: Keep this in sync with docs/signature-protocols.md!
304307
func (c *dockerClient) deleteOneSignature(url *url.URL) (missing bool, err error) {
305308
switch url.Scheme {
306309
case "file":

docker/docker_image_src.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ func (s *dockerImageSource) GetSignatures() ([][]byte, error) {
205205
return nil, err
206206
}
207207

208+
// NOTE: Keep this in sync with docs/signature-protocols.md!
208209
signatures := [][]byte{}
209210
for i := 0; ; i++ {
210211
url := signatureStorageURL(s.c.signatureBase, manifestDigest, i)
@@ -225,6 +226,7 @@ func (s *dockerImageSource) GetSignatures() ([][]byte, error) {
225226

226227
// getOneSignature downloads one signature from url.
227228
// If it successfully determines that the signature does not exist, returns with missing set to true and error set to nil.
229+
// NOTE: Keep this in sync with docs/signature-protocols.md!
228230
func (s *dockerImageSource) getOneSignature(url *url.URL) (signature []byte, missing bool, err error) {
229231
switch url.Scheme {
230232
case "file":

docker/lookaside.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ func configuredSignatureStorageBase(ctx *types.SystemContext, ref dockerReferenc
6363
if err != nil {
6464
return nil, errors.Wrapf(err, "Invalid signature storage URL %s", topLevel)
6565
}
66+
// NOTE: Keep this in sync with docs/signature-protocols.md!
6667
// FIXME? Restrict to explicitly supported schemes?
6768
repo := ref.ref.Name() // Note that this is without a tag or digest.
6869
if path.Clean(repo) != repo { // Coverage: This should not be reachable because /./ and /../ components are not valid in docker references
@@ -190,6 +191,7 @@ func (ns registryNamespace) signatureTopLevel(write bool) string {
190191

191192
// signatureStorageURL returns an URL usable for acessing signature index in base with known manifestDigest, or nil if not applicable.
192193
// Returns nil iff base == nil.
194+
// NOTE: Keep this in sync with docs/signature-protocols.md!
193195
func signatureStorageURL(base signatureStorageBase, manifestDigest digest.Digest, index int) *url.URL {
194196
if base == nil {
195197
return nil

docs/signature-protocols.md

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# Signature access protocols
2+
3+
The `github.com/containers/image` library supports signatures implemented as blobs “attached to” an image.
4+
Some image transports (local storage formats and remote procotocols) implement these signatures natively
5+
or trivially; for others, the protocol extensions described below are necessary.
6+
7+
## docker/distribution registries—separate storage
8+
9+
### Usage
10+
11+
Any existing docker/distribution registry, whether or not it natively supports signatures,
12+
can be augmented with separate signature storage by configuring a signature storage URL in [`registries.d`](registries.d.md).
13+
`registries.d` can be configured to use one storage URL for a whole docker/distribution server,
14+
or also separate URLs for smaller namespaces or individual repositories within the server
15+
(which e.g. allows image authors to manage their own signature storage while publishing
16+
the images on the public `docker.io` server).
17+
18+
The signature storage URL defines a root of a path hierarchy.
19+
It can be either a `file:///…` URL, pointing to a local directory structure,
20+
or a `http`/`https` URL, pointing to a remote server.
21+
`file:///` signature storage can be both read and written, `http`/`https` only supports reading.
22+
23+
The same path hierarchy is used in both cases, so the HTTP/HTTPS server can be
24+
a simple static web server serving a directory structure created by writing to a `file:///` signature storage.
25+
(This of course does not prevent other server implementations,
26+
e.g. a HTTP server reading signatures from a database.)
27+
28+
The usual workflow for producing and distributing images using the separate storage mechanism
29+
is to configure the repository in `registries.d` with `sigstore-staging` URL pointing to a private
30+
`file:///` staging area, and a `sigstore` URL pointing to a public web server.
31+
To publish an image, the image author would sign the image as necessary (e.g. using `skopeo copy`),
32+
and then copy the created directory structure from the `file:///` staging area
33+
to a subdirectory of a webroot of the public web server so that they are accessible using the public `sigstore` URL.
34+
The author would also instruct consumers of the image to, or provide a `registries.d` configuration file to,
35+
set up a `sigstore` URL pointing to the public web server.
36+
37+
### Path structure
38+
39+
Given a _base_ signature storage URL configured in `registries.d` as mentioned above,
40+
and a container image stored in a docker/distribution registry using the _fully-expanded_ name
41+
_hostname_`/`_namespaces_`/`_name_{`@`_digest_,`:`_tag_} (e.g. for `docker.io/library/busybox:latest`,
42+
_namespaces_ is `library`, even if the user refers to the image using the shorter syntax as `busybox:latest`),
43+
signatures are accessed using URLs of the form
44+
> _base_`/`_namespaces_`/`_name_`@`_digest-algo_`=`_digest-value_`/signature-`_index_
45+
46+
where _digest-algo_`:`_digest-value_ is a manifest digest usable for referencing the relevant image manifest
47+
(i.e. even if the user referenced the image using a tag,
48+
the signature storage is always disambiguated using digest references).
49+
Note that in the URLs used for signatures,
50+
_digest-algo_ and _digest-value_ are separated using the `=` character,
51+
not `:` like when acessing the manifest using the docker/distribution API.
52+
53+
Within the URL, _index_ is a decimal integer (in the canonical form), starting with 1.
54+
Signatures are stored at URLs with successive _index_ values; to read all of them, start with _index_=1,
55+
and continue reading signatures and increasing _index_ as long as signatures with these _index_ values exist.
56+
Similarly, to add one more signature to an image, find the first _index_ which does not exist, and
57+
then store the new signature using that _index_ value.
58+
59+
There is no way to list existing signatures other than iterating through the successive _index_ values,
60+
and no way to download all of the signatures at once.
61+
62+
### Examples
63+
64+
For a docker/distribution image available as `busybox@sha256:817a12c32a39bbe394944ba49de563e085f1d3c5266eb8e9723256bc4448680e`
65+
(or as `busybox:latest` if the `latest` tag points to to a manifest with the same digest),
66+
and with a `registries.d` configuration specifying a `sigstore` URL `https://example.com/sigstore` for the same image,
67+
the following URLs would be accessed to download all signatures:
68+
> - `https://example.com/sigstore/library/busybox@sha256=817a12c32a39bbe394944ba49de563e085f1d3c5266eb8e9723256bc4448680e/signature-1`
69+
> - `https://example.com/sigstore/library/busybox@sha256=817a12c32a39bbe394944ba49de563e085f1d3c5266eb8e9723256bc4448680e/signature-2`
70+
> -
71+
72+
For a docker/distribution image available as `example.com/ns1/ns2/ns3/repo@somedigest:digestvalue` and the same
73+
`sigstore` URL, the signatures would be available at
74+
> `https://example.com/sigstore/ns1/ns2/ns3/repo@somedigest=digestvalue/signature-1`
75+
76+
and so on.
77+
78+
## OpenShift-embedded registries
79+
80+
The OpenShift-embedded registry implements the ordinary docker/distribution API,
81+
and it also exposes images through the OpenShift REST API (available through the “API master” servers).
82+
83+
As of https://github.com/openshift/origin/pull/9181,
84+
signatures are exposed through the OpenShift API
85+
(i.e. to access the complete image, it is necessary to use both APIs,
86+
in particular to know the URLs for both the docker/distribution and the OpenShift API master endpoints).
87+
88+
To read the signature, any user with access to an image can use the `imagestreamimages` namespaced
89+
resource to read an `Image` object and its `Signatures` array. Use only the `ImageSignature` objects
90+
which have `Type` equal to `atomic`, and read the signature from `Content`; ignore the other fields of
91+
the `ImageSignature` object.
92+
93+
To add or remove signatures, use the cluster-wide (non-namespaced) `imagesignatures` resource,
94+
with `Type` set to `atomic` and `Content` set to the signature. Signature names must have the form
95+
_digest_`@`_per-image-name_, where _digest_ is an image manifest digest (OpenShift “image name”),
96+
and _per-image-name_ is any unique identifier.
97+
98+
Note that because signatures are stored within the cluster-wide image objects,
99+
i.e. different namespaces can not associate different sets of signatures to the same image,
100+
updating signatures requires a cluster-wide access to the `imagesignatures` resource
101+
(by default available to the `system:image-signer` role),
102+
and deleting signatures is strongly discouraged
103+
(it deletes the signature from all namespaces which contain the same image).
104+
105+
## (OpenShift) docker/distribution API extension
106+
107+
As of https://github.com/openshift/origin/pull/12504/ , the OpenShift-embedded registry also provides
108+
an extension of the docker/distribution API which allows simpler access to the signatures,
109+
using only the docker/distribution API endpoint.
110+
111+
This API is not inherently OpenShift-specific (e.g. the client does not need to know the OpenShift API endpoint,
112+
and credentials sufficient to access the docker/distribution API server are sufficient to access signatures as well),
113+
and in the future it will be the preferred way to implement signature storage in registries.
114+
115+
More detailed documentation of this API will be added later (after `github.com/containers/image` implements it).

0 commit comments

Comments
 (0)