Merge pull request #1687 from RichardScothern/signature-store

Remove signature store from registry.
This commit is contained in:
Richard Scothern 2016-05-31 09:09:39 -07:00
commit 641f102967
20 changed files with 64 additions and 410 deletions

View file

@ -24,7 +24,7 @@ storage:
cache: cache:
blobdescriptor: redis blobdescriptor: redis
filesystem: filesystem:
rootdirectory: /var/lib/registry rootdirectory: /var/lib/registry-hello-world-clean
maintenance: maintenance:
uploadpurging: uploadpurging:
enabled: false enabled: false

View file

@ -157,11 +157,6 @@ type Configuration struct {
// TrustKey is the signing key to use for adding the signature to // TrustKey is the signing key to use for adding the signature to
// schema1 manifests. // schema1 manifests.
TrustKey string `yaml:"signingkeyfile,omitempty"` TrustKey string `yaml:"signingkeyfile,omitempty"`
// DisableSignatureStore will cause all signatures attached to schema1 manifests
// to be ignored. Signatures will be generated on all schema1 manifest requests
// rather than only requests which converted schema2 to schema1.
DisableSignatureStore bool `yaml:"disablesignaturestore,omitempty"`
} `yaml:"schema1,omitempty"` } `yaml:"schema1,omitempty"`
} `yaml:"compatibility,omitempty"` } `yaml:"compatibility,omitempty"`
} }

View file

@ -243,7 +243,6 @@ information about each option that appears later in this page.
compatibility: compatibility:
schema1: schema1:
signingkeyfile: /etc/registry/key.json signingkeyfile: /etc/registry/key.json
disablesignaturestore: true
In some instances a configuration option is **optional** but it contains child In some instances a configuration option is **optional** but it contains child
options marked as **required**. This indicates that you can omit the parent with options marked as **required**. This indicates that you can omit the parent with
@ -1730,7 +1729,6 @@ To enable pulling private repositories (e.g. `batman/robin`) a username and pass
compatibility: compatibility:
schema1: schema1:
signingkeyfile: /etc/registry/key.json signingkeyfile: /etc/registry/key.json
disablesignaturestore: true
Configure handling of older and deprecated features. Each subsection Configure handling of older and deprecated features. Each subsection
defines a such a feature with configurable behavior. defines a such a feature with configurable behavior.
@ -1756,23 +1754,6 @@ defines a such a feature with configurable behavior.
startup. startup.
</td> </td>
</tr> </tr>
<tr>
<td>
<code>disablesignaturestore</code>
</td>
<td>
no
</td>
<td>
Disables storage of signatures attached to schema1 manifests. By default
signatures are detached from schema1 manifests, stored, and reattached
when the manifest is requested. When this is true, the storage is disabled
and a new signature is always generated for schema1 manifests using the
schema1 signing key. Disabling signature storage will cause all newly
uploaded signatures to be discarded. Existing stored signatures will not
be removed but they will not be re-attached to the corresponding manifest.
</td>
</tr>
</table> </table>
## Example: Development configuration ## Example: Development configuration

24
docs/deprecated.md Normal file
View file

@ -0,0 +1,24 @@
<!--[metadata]>
+++
title = "registry deprecation"
description = "describes deprecated functionality"
keywords = ["registry, manifest, images, signatures, repository, distribution, digest"]
+++
<![end-metadata]-->
# Docker Registry Deprecation
This document details functionality or components which are deprecated within
the registry.
### v2.5.0
The signature store has been removed from the registry. Since `v2.4.0` it has
been possible to configure the registry to generate manifest signatures rather
than load them from storage. In this version of the registry this becomes
the default behavior. Signatures which are attached to manifests on put are
not stored in the registry. This does not alter the functional behavior of
the registry.
Old signatures blobs can be removed from the registry storage by running the
garbage-collect subcommand.

View file

@ -61,12 +61,6 @@ type ManifestEnumerator interface {
Enumerate(ctx context.Context, ingester func(digest.Digest) error) error Enumerate(ctx context.Context, ingester func(digest.Digest) error) error
} }
// SignaturesGetter provides an interface for getting the signatures of a schema1 manifest. If the digest
// referred to is not a schema1 manifest, an error should be returned.
type SignaturesGetter interface {
GetSignatures(ctx context.Context, manifestDigest digest.Digest) ([]digest.Digest, error)
}
// Describable is an interface for descriptors // Describable is an interface for descriptors
type Describable interface { type Describable interface {
Descriptor() Descriptor Descriptor() Descriptor

View file

@ -20,7 +20,12 @@ import (
func TestListener(t *testing.T) { func TestListener(t *testing.T) {
ctx := context.Background() ctx := context.Background()
registry, err := storage.NewRegistry(ctx, inmemory.New(), storage.BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), storage.EnableDelete, storage.EnableRedirect) k, err := libtrust.GenerateECP256PrivateKey()
if err != nil {
t.Fatal(err)
}
registry, err := storage.NewRegistry(ctx, inmemory.New(), storage.BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), storage.EnableDelete, storage.EnableRedirect, storage.Schema1SigningKey(k))
if err != nil { if err != nil {
t.Fatalf("error creating registry: %v", err) t.Fatalf("error creating registry: %v", err)
} }

View file

@ -1067,13 +1067,13 @@ func testManifestAPISchema1(t *testing.T, env *testEnv, imageName reference.Name
t.Fatalf("error decoding fetched manifest: %v", err) t.Fatalf("error decoding fetched manifest: %v", err)
} }
// check two signatures were roundtripped // check only 1 signature is returned
signatures, err = fetchedManifestByDigest.Signatures() signatures, err = fetchedManifestByDigest.Signatures()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if len(signatures) != 2 { if len(signatures) != 1 {
t.Fatalf("expected 2 signature from manifest, got: %d", len(signatures)) t.Fatalf("expected 2 signature from manifest, got: %d", len(signatures))
} }

View file

@ -155,6 +155,7 @@ func NewApp(ctx context.Context, config *configuration.Configuration) *App {
app.configureRedis(config) app.configureRedis(config)
app.configureLogHook(config) app.configureLogHook(config)
options := registrymiddleware.GetRegistryOptions()
if config.Compatibility.Schema1.TrustKey != "" { if config.Compatibility.Schema1.TrustKey != "" {
app.trustKey, err = libtrust.LoadKeyFile(config.Compatibility.Schema1.TrustKey) app.trustKey, err = libtrust.LoadKeyFile(config.Compatibility.Schema1.TrustKey)
if err != nil { if err != nil {
@ -169,6 +170,8 @@ func NewApp(ctx context.Context, config *configuration.Configuration) *App {
} }
} }
options = append(options, storage.Schema1SigningKey(app.trustKey))
if config.HTTP.Host != "" { if config.HTTP.Host != "" {
u, err := url.Parse(config.HTTP.Host) u, err := url.Parse(config.HTTP.Host)
if err != nil { if err != nil {
@ -177,17 +180,10 @@ func NewApp(ctx context.Context, config *configuration.Configuration) *App {
app.httpHost = *u app.httpHost = *u
} }
options := registrymiddleware.GetRegistryOptions()
if app.isCache { if app.isCache {
options = append(options, storage.DisableDigestResumption) options = append(options, storage.DisableDigestResumption)
} }
if config.Compatibility.Schema1.DisableSignatureStore {
options = append(options, storage.DisableSchema1Signatures)
options = append(options, storage.Schema1SigningKey(app.trustKey))
}
// configure deletion // configure deletion
if d, ok := config.Storage["delete"]; ok { if d, ok := config.Storage["delete"]; ok {
e, ok := d["enabled"] e, ok := d["enabled"]

View file

@ -60,12 +60,6 @@ func (sm statsManifest) Put(ctx context.Context, manifest distribution.Manifest,
return sm.manifests.Put(ctx, manifest) return sm.manifests.Put(ctx, manifest)
} }
/*func (sm statsManifest) Enumerate(ctx context.Context, manifests []distribution.Manifest, last distribution.Manifest) (n int, err error) {
sm.stats["enumerate"]++
return sm.manifests.Enumerate(ctx, manifests, last)
}
*/
type mockChallenger struct { type mockChallenger struct {
sync.Mutex sync.Mutex
count int count int
@ -75,7 +69,6 @@ type mockChallenger struct {
func (m *mockChallenger) tryEstablishChallenges(context.Context) error { func (m *mockChallenger) tryEstablishChallenges(context.Context) error {
m.Lock() m.Lock()
defer m.Unlock() defer m.Unlock()
m.count++ m.count++
return nil return nil
} }
@ -93,9 +86,15 @@ func newManifestStoreTestEnv(t *testing.T, name, tag string) *manifestStoreTestE
if err != nil { if err != nil {
t.Fatalf("unable to parse reference: %s", err) t.Fatalf("unable to parse reference: %s", err)
} }
k, err := libtrust.GenerateECP256PrivateKey()
if err != nil {
t.Fatal(err)
}
ctx := context.Background() ctx := context.Background()
truthRegistry, err := storage.NewRegistry(ctx, inmemory.New(), storage.BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider())) truthRegistry, err := storage.NewRegistry(ctx, inmemory.New(),
storage.BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()),
storage.Schema1SigningKey(k))
if err != nil { if err != nil {
t.Fatalf("error creating registry: %v", err) t.Fatalf("error creating registry: %v", err)
} }
@ -117,7 +116,7 @@ func newManifestStoreTestEnv(t *testing.T, name, tag string) *manifestStoreTestE
t.Fatalf(err.Error()) t.Fatalf(err.Error())
} }
localRegistry, err := storage.NewRegistry(ctx, inmemory.New(), storage.BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), storage.EnableRedirect, storage.DisableDigestResumption) localRegistry, err := storage.NewRegistry(ctx, inmemory.New(), storage.BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), storage.EnableRedirect, storage.DisableDigestResumption, storage.Schema1SigningKey(k))
if err != nil { if err != nil {
t.Fatalf("error creating registry: %v", err) t.Fatalf("error creating registry: %v", err)
} }

View file

@ -69,7 +69,7 @@ var GCCmd = &cobra.Command{
os.Exit(1) os.Exit(1)
} }
registry, err := storage.NewRegistry(ctx, driver, storage.DisableSchema1Signatures, storage.Schema1SigningKey(k)) registry, err := storage.NewRegistry(ctx, driver, storage.Schema1SigningKey(k))
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "failed to construct registry: %v", err) fmt.Fprintf(os.Stderr, "failed to construct registry: %v", err)
os.Exit(1) os.Exit(1)

View file

@ -75,7 +75,6 @@ func (bs *blobStore) Put(ctx context.Context, mediaType string, p []byte) (distr
} }
// TODO(stevvooe): Write out mediatype here, as well. // TODO(stevvooe): Write out mediatype here, as well.
return distribution.Descriptor{ return distribution.Descriptor{
Size: int64(len(p)), Size: int64(len(p)),

View file

@ -6,7 +6,6 @@ import (
"github.com/docker/distribution" "github.com/docker/distribution"
"github.com/docker/distribution/context" "github.com/docker/distribution/context"
"github.com/docker/distribution/digest" "github.com/docker/distribution/digest"
"github.com/docker/distribution/manifest/schema1"
"github.com/docker/distribution/manifest/schema2" "github.com/docker/distribution/manifest/schema2"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
"github.com/docker/distribution/registry/storage/driver" "github.com/docker/distribution/registry/storage/driver"
@ -71,22 +70,6 @@ func MarkAndSweep(ctx context.Context, storageDriver driver.StorageDriver, regis
} }
switch manifest.(type) { switch manifest.(type) {
case *schema1.SignedManifest:
signaturesGetter, ok := manifestService.(distribution.SignaturesGetter)
if !ok {
return fmt.Errorf("unable to convert ManifestService into SignaturesGetter")
}
signatures, err := signaturesGetter.GetSignatures(ctx, dgst)
if err != nil {
return fmt.Errorf("failed to get signatures for signed manifest: %v", err)
}
for _, signatureDigest := range signatures {
if dryRun {
emit("%s: marking signature %s", repoName, signatureDigest)
}
markSet[signatureDigest] = struct{}{}
}
break
case *schema2.DeserializedManifest: case *schema2.DeserializedManifest:
config := manifest.(*schema2.DeserializedManifest).Config config := manifest.(*schema2.DeserializedManifest).Config
if dryRun { if dryRun {

View file

@ -12,6 +12,7 @@ import (
"github.com/docker/distribution/registry/storage/driver" "github.com/docker/distribution/registry/storage/driver"
"github.com/docker/distribution/registry/storage/driver/inmemory" "github.com/docker/distribution/registry/storage/driver/inmemory"
"github.com/docker/distribution/testutil" "github.com/docker/distribution/testutil"
"github.com/docker/libtrust"
) )
type image struct { type image struct {
@ -22,7 +23,11 @@ type image struct {
func createRegistry(t *testing.T, driver driver.StorageDriver) distribution.Namespace { func createRegistry(t *testing.T, driver driver.StorageDriver) distribution.Namespace {
ctx := context.Background() ctx := context.Background()
registry, err := NewRegistry(ctx, driver, EnableDelete) k, err := libtrust.GenerateECP256PrivateKey()
if err != nil {
t.Fatal(err)
}
registry, err := NewRegistry(ctx, driver, EnableDelete, Schema1SigningKey(k))
if err != nil { if err != nil {
t.Fatalf("Failed to construct namespace") t.Fatalf("Failed to construct namespace")
} }
@ -139,13 +144,13 @@ func TestNoDeletionNoEffect(t *testing.T) {
ctx := context.Background() ctx := context.Background()
inmemoryDriver := inmemory.New() inmemoryDriver := inmemory.New()
registry := createRegistry(t, inmemoryDriver) registry := createRegistry(t, inmemory.New())
repo := makeRepository(t, registry, "palailogos") repo := makeRepository(t, registry, "palailogos")
manifestService, err := repo.Manifests(ctx) manifestService, err := repo.Manifests(ctx)
image1 := uploadRandomSchema1Image(t, repo) image1 := uploadRandomSchema1Image(t, repo)
image2 := uploadRandomSchema1Image(t, repo) image2 := uploadRandomSchema1Image(t, repo)
image3 := uploadRandomSchema2Image(t, repo) uploadRandomSchema2Image(t, repo)
// construct manifestlist for fun. // construct manifestlist for fun.
blobstatter := registry.BlobStatter() blobstatter := registry.BlobStatter()
@ -160,20 +165,17 @@ func TestNoDeletionNoEffect(t *testing.T) {
t.Fatalf("Failed to add manifest list: %v", err) t.Fatalf("Failed to add manifest list: %v", err)
} }
before := allBlobs(t, registry)
// Run GC // Run GC
err = MarkAndSweep(context.Background(), inmemoryDriver, registry, false) err = MarkAndSweep(context.Background(), inmemoryDriver, registry, false)
if err != nil { if err != nil {
t.Fatalf("Failed mark and sweep: %v", err) t.Fatalf("Failed mark and sweep: %v", err)
} }
blobs := allBlobs(t, registry) after := allBlobs(t, registry)
if len(before) != len(after) {
// the +1 at the end is for the manifestList t.Fatalf("Garbage collection affected storage: %d != %d", len(before), len(after))
// the first +3 at the end for each manifest's blob
// the second +3 at the end for each manifest's signature/config layer
totalBlobCount := len(image1.layers) + len(image2.layers) + len(image3.layers) + 1 + 3 + 3
if len(blobs) != totalBlobCount {
t.Fatalf("Garbage collection affected storage")
} }
} }

View file

@ -2,7 +2,6 @@ package storage
import ( import (
"fmt" "fmt"
"path"
"encoding/json" "encoding/json"
"github.com/docker/distribution" "github.com/docker/distribution"
@ -12,7 +11,6 @@ import (
"github.com/docker/distribution/manifest/manifestlist" "github.com/docker/distribution/manifest/manifestlist"
"github.com/docker/distribution/manifest/schema1" "github.com/docker/distribution/manifest/schema1"
"github.com/docker/distribution/manifest/schema2" "github.com/docker/distribution/manifest/schema2"
"github.com/docker/distribution/registry/storage/driver"
) )
// A ManifestHandler gets and puts manifests of a particular type. // A ManifestHandler gets and puts manifests of a particular type.
@ -141,48 +139,3 @@ func (ms *manifestStore) Enumerate(ctx context.Context, ingester func(digest.Dig
}) })
return err return err
} }
// Only valid for schema1 signed manifests
func (ms *manifestStore) GetSignatures(ctx context.Context, manifestDigest digest.Digest) ([]digest.Digest, error) {
// sanity check that digest refers to a schema1 digest
manifest, err := ms.Get(ctx, manifestDigest)
if err != nil {
return nil, err
}
if _, ok := manifest.(*schema1.SignedManifest); !ok {
return nil, fmt.Errorf("digest %v is not for schema1 manifest", manifestDigest)
}
signaturesPath, err := pathFor(manifestSignaturesPathSpec{
name: ms.repository.Named().Name(),
revision: manifestDigest,
})
if err != nil {
return nil, err
}
var digests []digest.Digest
alg := string(digest.SHA256)
signaturePaths, err := ms.blobStore.driver.List(ctx, path.Join(signaturesPath, alg))
switch err.(type) {
case nil:
break
case driver.PathNotFoundError:
// Manifest may have been pushed with signature store disabled
return digests, nil
default:
return nil, err
}
for _, sigPath := range signaturePaths {
sigdigest, err := digest.ParseDigest(alg + ":" + path.Base(sigPath))
if err != nil {
// merely found not a digest
continue
}
digests = append(digests, sigdigest)
}
return digests, nil
}

View file

@ -52,15 +52,11 @@ func newManifestStoreTestEnv(t *testing.T, name reference.Named, tag string, opt
} }
func TestManifestStorage(t *testing.T) { func TestManifestStorage(t *testing.T) {
testManifestStorage(t, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableDelete, EnableRedirect)
}
func TestManifestStorageDisabledSignatures(t *testing.T) {
k, err := libtrust.GenerateECP256PrivateKey() k, err := libtrust.GenerateECP256PrivateKey()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
testManifestStorage(t, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableDelete, EnableRedirect, DisableSchema1Signatures, Schema1SigningKey(k)) testManifestStorage(t, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableDelete, EnableRedirect, Schema1SigningKey(k))
} }
func testManifestStorage(t *testing.T, options ...RegistryOption) { func testManifestStorage(t *testing.T, options ...RegistryOption) {
@ -71,7 +67,6 @@ func testManifestStorage(t *testing.T, options ...RegistryOption) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
equalSignatures := env.registry.(*registry).schema1SignaturesEnabled
m := schema1.Manifest{ m := schema1.Manifest{
Versioned: manifest.Versioned{ Versioned: manifest.Versioned{
@ -175,12 +170,6 @@ func testManifestStorage(t *testing.T, options ...RegistryOption) {
t.Fatalf("fetched payload does not match original payload: %q != %q", fetchedManifest.Canonical, sm.Canonical) t.Fatalf("fetched payload does not match original payload: %q != %q", fetchedManifest.Canonical, sm.Canonical)
} }
if equalSignatures {
if !reflect.DeepEqual(fetchedManifest, sm) {
t.Fatalf("fetched manifest not equal: %#v != %#v", fetchedManifest.Manifest, sm.Manifest)
}
}
_, pl, err := fetchedManifest.Payload() _, pl, err := fetchedManifest.Payload()
if err != nil { if err != nil {
t.Fatalf("error getting payload %#v", err) t.Fatalf("error getting payload %#v", err)
@ -223,12 +212,6 @@ func testManifestStorage(t *testing.T, options ...RegistryOption) {
t.Fatalf("fetched manifest not equal: %q != %q", byDigestManifest.Canonical, fetchedManifest.Canonical) t.Fatalf("fetched manifest not equal: %q != %q", byDigestManifest.Canonical, fetchedManifest.Canonical)
} }
if equalSignatures {
if !reflect.DeepEqual(fetchedByDigest, fetchedManifest) {
t.Fatalf("fetched manifest not equal: %#v != %#v", fetchedByDigest, fetchedManifest)
}
}
sigs, err := fetchedJWS.Signatures() sigs, err := fetchedJWS.Signatures()
if err != nil { if err != nil {
t.Fatalf("unable to extract signatures: %v", err) t.Fatalf("unable to extract signatures: %v", err)
@ -285,17 +268,6 @@ func testManifestStorage(t *testing.T, options ...RegistryOption) {
t.Fatalf("unexpected error verifying manifest: %v", err) t.Fatalf("unexpected error verifying manifest: %v", err)
} }
// Assemble our payload and two signatures to get what we expect!
expectedJWS, err := libtrust.NewJSONSignature(payload, sigs[0], sigs2[0])
if err != nil {
t.Fatalf("unexpected error merging jws: %v", err)
}
expectedSigs, err := expectedJWS.Signatures()
if err != nil {
t.Fatalf("unexpected error getting expected signatures: %v", err)
}
_, pl, err = fetched.Payload() _, pl, err = fetched.Payload()
if err != nil { if err != nil {
t.Fatalf("error getting payload %#v", err) t.Fatalf("error getting payload %#v", err)
@ -315,19 +287,6 @@ func testManifestStorage(t *testing.T, options ...RegistryOption) {
t.Fatalf("payloads are not equal") t.Fatalf("payloads are not equal")
} }
if equalSignatures {
receivedSigs, err := receivedJWS.Signatures()
if err != nil {
t.Fatalf("error getting signatures: %v", err)
}
for i, sig := range receivedSigs {
if !bytes.Equal(sig, expectedSigs[i]) {
t.Fatalf("mismatched signatures from remote: %v != %v", string(sig), string(expectedSigs[i]))
}
}
}
// Test deleting manifests // Test deleting manifests
err = ms.Delete(ctx, dgst) err = ms.Delete(ctx, dgst)
if err != nil { if err != nil {

View file

@ -30,8 +30,6 @@ const (
// revisions // revisions
// -> <manifest digest path> // -> <manifest digest path>
// -> link // -> link
// -> signatures
// <algorithm>/<digest>/link
// tags/<tag> // tags/<tag>
// -> current/link // -> current/link
// -> index // -> index
@ -62,8 +60,7 @@ const (
// //
// The third component of the repository directory is the manifests store, // The third component of the repository directory is the manifests store,
// which is made up of a revision store and tag store. Manifests are stored in // which is made up of a revision store and tag store. Manifests are stored in
// the blob store and linked into the revision store. Signatures are separated // the blob store and linked into the revision store.
// from the manifest payload data and linked into the blob store, as well.
// While the registry can save all revisions of a manifest, no relationship is // While the registry can save all revisions of a manifest, no relationship is
// implied as to the ordering of changes to a manifest. The tag store provides // implied as to the ordering of changes to a manifest. The tag store provides
// support for name, tag lookups of manifests, using "current/link" under a // support for name, tag lookups of manifests, using "current/link" under a
@ -77,8 +74,6 @@ const (
// manifestRevisionsPathSpec: <root>/v2/repositories/<name>/_manifests/revisions/ // manifestRevisionsPathSpec: <root>/v2/repositories/<name>/_manifests/revisions/
// manifestRevisionPathSpec: <root>/v2/repositories/<name>/_manifests/revisions/<algorithm>/<hex digest>/ // manifestRevisionPathSpec: <root>/v2/repositories/<name>/_manifests/revisions/<algorithm>/<hex digest>/
// manifestRevisionLinkPathSpec: <root>/v2/repositories/<name>/_manifests/revisions/<algorithm>/<hex digest>/link // manifestRevisionLinkPathSpec: <root>/v2/repositories/<name>/_manifests/revisions/<algorithm>/<hex digest>/link
// manifestSignaturesPathSpec: <root>/v2/repositories/<name>/_manifests/revisions/<algorithm>/<hex digest>/signatures/
// manifestSignatureLinkPathSpec: <root>/v2/repositories/<name>/_manifests/revisions/<algorithm>/<hex digest>/signatures/<algorithm>/<hex digest>/link
// //
// Tags: // Tags:
// //
@ -148,33 +143,6 @@ func pathFor(spec pathSpec) (string, error) {
} }
return path.Join(root, "link"), nil return path.Join(root, "link"), nil
case manifestSignaturesPathSpec:
root, err := pathFor(manifestRevisionPathSpec{
name: v.name,
revision: v.revision,
})
if err != nil {
return "", err
}
return path.Join(root, "signatures"), nil
case manifestSignatureLinkPathSpec:
root, err := pathFor(manifestSignaturesPathSpec{
name: v.name,
revision: v.revision,
})
if err != nil {
return "", err
}
signatureComponents, err := digestPathComponents(v.signature, false)
if err != nil {
return "", err
}
return path.Join(root, path.Join(append(signatureComponents, "link")...)), nil
case manifestTagsPathSpec: case manifestTagsPathSpec:
return path.Join(append(repoPrefix, v.name, "_manifests", "tags")...), nil return path.Join(append(repoPrefix, v.name, "_manifests", "tags")...), nil
case manifestTagPathSpec: case manifestTagPathSpec:
@ -325,26 +293,6 @@ type manifestRevisionLinkPathSpec struct {
func (manifestRevisionLinkPathSpec) pathSpec() {} func (manifestRevisionLinkPathSpec) pathSpec() {}
// manifestSignaturesPathSpec describes the path components for the directory
// containing all the signatures for the target blob. Entries are named with
// the underlying key id.
type manifestSignaturesPathSpec struct {
name string
revision digest.Digest
}
func (manifestSignaturesPathSpec) pathSpec() {}
// manifestSignatureLinkPathSpec describes the path components used to look up
// a signature file by the hash of its blob.
type manifestSignatureLinkPathSpec struct {
name string
revision digest.Digest
signature digest.Digest
}
func (manifestSignatureLinkPathSpec) pathSpec() {}
// manifestTagsPathSpec describes the path elements required to point to the // manifestTagsPathSpec describes the path elements required to point to the
// manifest tags directory. // manifest tags directory.
type manifestTagsPathSpec struct { type manifestTagsPathSpec struct {

View file

@ -26,21 +26,6 @@ func TestPathMapper(t *testing.T) {
}, },
expected: "/docker/registry/v2/repositories/foo/bar/_manifests/revisions/sha256/abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789/link", expected: "/docker/registry/v2/repositories/foo/bar/_manifests/revisions/sha256/abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789/link",
}, },
{
spec: manifestSignatureLinkPathSpec{
name: "foo/bar",
revision: "sha256:abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789",
signature: "sha256:abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789",
},
expected: "/docker/registry/v2/repositories/foo/bar/_manifests/revisions/sha256/abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789/signatures/sha256/abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789/link",
},
{
spec: manifestSignaturesPathSpec{
name: "foo/bar",
revision: "sha256:abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789",
},
expected: "/docker/registry/v2/repositories/foo/bar/_manifests/revisions/sha256/abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789/signatures",
},
{ {
spec: manifestTagsPathSpec{ spec: manifestTagsPathSpec{
name: "foo/bar", name: "foo/bar",
@ -113,7 +98,7 @@ func TestPathMapper(t *testing.T) {
// Add a few test cases to ensure we cover some errors // Add a few test cases to ensure we cover some errors
// Specify a path that requires a revision and get a digest validation error. // Specify a path that requires a revision and get a digest validation error.
badpath, err := pathFor(manifestSignaturesPathSpec{ badpath, err := pathFor(manifestRevisionPathSpec{
name: "foo/bar", name: "foo/bar",
}) })

View file

@ -18,7 +18,6 @@ type registry struct {
blobDescriptorCacheProvider cache.BlobDescriptorCacheProvider blobDescriptorCacheProvider cache.BlobDescriptorCacheProvider
deleteEnabled bool deleteEnabled bool
resumableDigestEnabled bool resumableDigestEnabled bool
schema1SignaturesEnabled bool
schema1SigningKey libtrust.PrivateKey schema1SigningKey libtrust.PrivateKey
blobDescriptorServiceFactory distribution.BlobDescriptorServiceFactory blobDescriptorServiceFactory distribution.BlobDescriptorServiceFactory
} }
@ -47,17 +46,8 @@ func DisableDigestResumption(registry *registry) error {
return nil return nil
} }
// DisableSchema1Signatures is a functional option for NewRegistry. It disables
// signature storage and ensures all schema1 manifests will only be returned
// with a signature from a provided signing key.
func DisableSchema1Signatures(registry *registry) error {
registry.schema1SignaturesEnabled = false
return nil
}
// Schema1SigningKey returns a functional option for NewRegistry. It sets the // Schema1SigningKey returns a functional option for NewRegistry. It sets the
// signing key for adding a signature to all schema1 manifests. This should be // key for signing all schema1 manifests.
// used in conjunction with disabling signature store.
func Schema1SigningKey(key libtrust.PrivateKey) RegistryOption { func Schema1SigningKey(key libtrust.PrivateKey) RegistryOption {
return func(registry *registry) error { return func(registry *registry) error {
registry.schema1SigningKey = key registry.schema1SigningKey = key
@ -116,9 +106,8 @@ func NewRegistry(ctx context.Context, driver storagedriver.StorageDriver, option
statter: statter, statter: statter,
pathFn: bs.path, pathFn: bs.path,
}, },
statter: statter, statter: statter,
resumableDigestEnabled: true, resumableDigestEnabled: true,
schema1SignaturesEnabled: true,
} }
for _, option := range options { for _, option := range options {
@ -231,11 +220,6 @@ func (repo *repository) Manifests(ctx context.Context, options ...distribution.M
ctx: ctx, ctx: ctx,
repository: repo, repository: repo,
blobStore: blobStore, blobStore: blobStore,
signatures: &signatureStore{
ctx: ctx,
repository: repo,
blobStore: repo.blobStore,
},
}, },
schema2Handler: &schema2ManifestHandler{ schema2Handler: &schema2ManifestHandler{
ctx: ctx, ctx: ctx,

View file

@ -1,131 +0,0 @@
package storage
import (
"path"
"sync"
"github.com/docker/distribution/context"
"github.com/docker/distribution/digest"
)
type signatureStore struct {
repository *repository
blobStore *blobStore
ctx context.Context
}
func (s *signatureStore) Get(dgst digest.Digest) ([][]byte, error) {
signaturesPath, err := pathFor(manifestSignaturesPathSpec{
name: s.repository.Named().Name(),
revision: dgst,
})
if err != nil {
return nil, err
}
// Need to append signature digest algorithm to path to get all items.
// Perhaps, this should be in the pathMapper but it feels awkward. This
// can be eliminated by implementing listAll on drivers.
signaturesPath = path.Join(signaturesPath, "sha256")
signaturePaths, err := s.blobStore.driver.List(s.ctx, signaturesPath)
if err != nil {
return nil, err
}
var wg sync.WaitGroup
type result struct {
index int
signature []byte
err error
}
ch := make(chan result)
bs := s.linkedBlobStore(s.ctx, dgst)
for i, sigPath := range signaturePaths {
sigdgst, err := digest.ParseDigest("sha256:" + path.Base(sigPath))
if err != nil {
context.GetLogger(s.ctx).Errorf("could not get digest from path: %q, skipping", sigPath)
continue
}
wg.Add(1)
go func(idx int, sigdgst digest.Digest) {
defer wg.Done()
context.GetLogger(s.ctx).
Debugf("fetching signature %q", sigdgst)
r := result{index: idx}
if p, err := bs.Get(s.ctx, sigdgst); err != nil {
context.GetLogger(s.ctx).
Errorf("error fetching signature %q: %v", sigdgst, err)
r.err = err
} else {
r.signature = p
}
ch <- r
}(i, sigdgst)
}
done := make(chan struct{})
go func() {
wg.Wait()
close(done)
}()
// aggregrate the results
signatures := make([][]byte, len(signaturePaths))
loop:
for {
select {
case result := <-ch:
signatures[result.index] = result.signature
if result.err != nil && err == nil {
// only set the first one.
err = result.err
}
case <-done:
break loop
}
}
return signatures, err
}
func (s *signatureStore) Put(dgst digest.Digest, signatures ...[]byte) error {
bs := s.linkedBlobStore(s.ctx, dgst)
for _, signature := range signatures {
if _, err := bs.Put(s.ctx, "application/json", signature); err != nil {
return err
}
}
return nil
}
// linkedBlobStore returns the namedBlobStore of the signatures for the
// manifest with the given digest. Effectively, each signature link path
// layout is a unique linked blob store.
func (s *signatureStore) linkedBlobStore(ctx context.Context, revision digest.Digest) *linkedBlobStore {
linkpath := func(name string, dgst digest.Digest) (string, error) {
return pathFor(manifestSignatureLinkPathSpec{
name: name,
revision: revision,
signature: dgst,
})
}
return &linkedBlobStore{
ctx: ctx,
repository: s.repository,
blobStore: s.blobStore,
blobAccessController: &linkedBlobStatter{
blobStore: s.blobStore,
repository: s.repository,
linkPathFns: []linkPathFunc{linkpath},
},
linkPathFns: []linkPathFunc{linkpath},
}
}

View file

@ -18,7 +18,6 @@ type signedManifestHandler struct {
repository *repository repository *repository
blobStore *linkedBlobStore blobStore *linkedBlobStore
ctx context.Context ctx context.Context
signatures *signatureStore
} }
var _ ManifestHandler = &signedManifestHandler{} var _ ManifestHandler = &signedManifestHandler{}
@ -30,13 +29,6 @@ func (ms *signedManifestHandler) Unmarshal(ctx context.Context, dgst digest.Dige
signatures [][]byte signatures [][]byte
err error err error
) )
if ms.repository.schema1SignaturesEnabled {
// Fetch the signatures for the manifest
signatures, err = ms.signatures.Get(dgst)
if err != nil {
return nil, err
}
}
jsig, err := libtrust.NewJSONSignature(content, signatures...) jsig, err := libtrust.NewJSONSignature(content, signatures...)
if err != nil { if err != nil {
@ -47,8 +39,6 @@ func (ms *signedManifestHandler) Unmarshal(ctx context.Context, dgst digest.Dige
if err := jsig.Sign(ms.repository.schema1SigningKey); err != nil { if err := jsig.Sign(ms.repository.schema1SigningKey); err != nil {
return nil, err return nil, err
} }
} else if !ms.repository.schema1SignaturesEnabled {
return nil, fmt.Errorf("missing signing key with signature store disabled")
} }
// Extract the pretty JWS // Extract the pretty JWS
@ -90,18 +80,6 @@ func (ms *signedManifestHandler) Put(ctx context.Context, manifest distribution.
return "", err return "", err
} }
if ms.repository.schema1SignaturesEnabled {
// Grab each json signature and store them.
signatures, err := sm.Signatures()
if err != nil {
return "", err
}
if err := ms.signatures.Put(revision.Digest, signatures...); err != nil {
return "", err
}
}
return revision.Digest, nil return revision.Digest, nil
} }