Merge pull request #1420 from dmcgowan/configurable-trust-key

Add option to disable signatures
This commit is contained in:
Aaron Lehmann 2016-02-10 16:15:59 -08:00
commit 2177a6a1bf
4 changed files with 115 additions and 33 deletions

View file

@ -155,11 +155,18 @@ func NewApp(ctx context.Context, config *configuration.Configuration) *App {
app.configureRedis(config) app.configureRedis(config)
app.configureLogHook(config) app.configureLogHook(config)
// Generate an ephemeral key to be used for signing converted manifests if config.Compatibility.Schema1.TrustKey != "" {
// for clients that don't support schema2. app.trustKey, err = libtrust.LoadKeyFile(config.Compatibility.Schema1.TrustKey)
app.trustKey, err = libtrust.GenerateECP256PrivateKey() if err != nil {
if err != nil { panic(fmt.Sprintf(`could not load schema1 "signingkey" parameter: %v`, err))
panic(err) }
} else {
// Generate an ephemeral key to be used for signing converted manifests
// for clients that don't support schema2.
app.trustKey, err = libtrust.GenerateECP256PrivateKey()
if err != nil {
panic(err)
}
} }
if config.HTTP.Host != "" { if config.HTTP.Host != "" {
@ -176,6 +183,11 @@ func NewApp(ctx context.Context, config *configuration.Configuration) *App {
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

@ -28,11 +28,10 @@ type manifestStoreTestEnv struct {
tag string tag string
} }
func newManifestStoreTestEnv(t *testing.T, name reference.Named, tag string) *manifestStoreTestEnv { func newManifestStoreTestEnv(t *testing.T, name reference.Named, tag string, options ...RegistryOption) *manifestStoreTestEnv {
ctx := context.Background() ctx := context.Background()
driver := inmemory.New() driver := inmemory.New()
registry, err := NewRegistry(ctx, driver, BlobDescriptorCacheProvider( registry, err := NewRegistry(ctx, driver, options...)
memory.NewInMemoryBlobDescriptorCacheProvider()), EnableDelete, EnableRedirect)
if err != nil { if err != nil {
t.Fatalf("error creating registry: %v", err) t.Fatalf("error creating registry: %v", err)
} }
@ -53,13 +52,26 @@ func newManifestStoreTestEnv(t *testing.T, name reference.Named, tag string) *ma
} }
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()
if err != nil {
t.Fatal(err)
}
testManifestStorage(t, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableDelete, EnableRedirect, DisableSchema1Signatures, Schema1SigningKey(k))
}
func testManifestStorage(t *testing.T, options ...RegistryOption) {
repoName, _ := reference.ParseNamed("foo/bar") repoName, _ := reference.ParseNamed("foo/bar")
env := newManifestStoreTestEnv(t, repoName, "thetag") env := newManifestStoreTestEnv(t, repoName, "thetag", options...)
ctx := context.Background() ctx := context.Background()
ms, err := env.repository.Manifests(ctx) ms, err := env.repository.Manifests(ctx)
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{
@ -159,8 +171,14 @@ func TestManifestStorage(t *testing.T) {
t.Fatalf("unexpected manifest type from signedstore") t.Fatalf("unexpected manifest type from signedstore")
} }
if !reflect.DeepEqual(fetchedManifest, sm) { if !bytes.Equal(fetchedManifest.Canonical, sm.Canonical) {
t.Fatalf("fetched manifest not equal: %#v != %#v", fetchedManifest, sm) 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()
@ -196,8 +214,19 @@ func TestManifestStorage(t *testing.T) {
t.Fatalf("unexpected error fetching manifest by digest: %v", err) t.Fatalf("unexpected error fetching manifest by digest: %v", err)
} }
if !reflect.DeepEqual(fetchedByDigest, fetchedManifest) { byDigestManifest, ok := fetchedByDigest.(*schema1.SignedManifest)
t.Fatalf("fetched manifest not equal: %#v != %#v", fetchedByDigest, fetchedManifest) if !ok {
t.Fatalf("unexpected manifest type from signedstore")
}
if !bytes.Equal(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()
@ -286,14 +315,16 @@ func TestManifestStorage(t *testing.T) {
t.Fatalf("payloads are not equal") t.Fatalf("payloads are not equal")
} }
receivedSigs, err := receivedJWS.Signatures() if equalSignatures {
if err != nil { receivedSigs, err := receivedJWS.Signatures()
t.Fatalf("error getting signatures: %v", err) if err != nil {
} t.Fatalf("error getting signatures: %v", err)
}
for i, sig := range receivedSigs { for i, sig := range receivedSigs {
if !bytes.Equal(sig, expectedSigs[i]) { if !bytes.Equal(sig, expectedSigs[i]) {
t.Fatalf("mismatched signatures from remote: %v != %v", string(sig), string(expectedSigs[i])) t.Fatalf("mismatched signatures from remote: %v != %v", string(sig), string(expectedSigs[i]))
}
} }
} }

View file

@ -6,6 +6,7 @@ import (
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
"github.com/docker/distribution/registry/storage/cache" "github.com/docker/distribution/registry/storage/cache"
storagedriver "github.com/docker/distribution/registry/storage/driver" storagedriver "github.com/docker/distribution/registry/storage/driver"
"github.com/docker/libtrust"
) )
// registry is the top-level implementation of Registry for use in the storage // registry is the top-level implementation of Registry for use in the storage
@ -17,6 +18,8 @@ type registry struct {
blobDescriptorCacheProvider cache.BlobDescriptorCacheProvider blobDescriptorCacheProvider cache.BlobDescriptorCacheProvider
deleteEnabled bool deleteEnabled bool
resumableDigestEnabled bool resumableDigestEnabled bool
schema1SignaturesEnabled bool
schema1SigningKey libtrust.PrivateKey
} }
// RegistryOption is the type used for functional options for NewRegistry. // RegistryOption is the type used for functional options for NewRegistry.
@ -43,6 +46,24 @@ 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
// signing key for adding a signature to all schema1 manifests. This should be
// used in conjunction with disabling signature store.
func Schema1SigningKey(key libtrust.PrivateKey) RegistryOption {
return func(registry *registry) error {
registry.schema1SigningKey = key
return nil
}
}
// BlobDescriptorCacheProvider returns a functional option for // BlobDescriptorCacheProvider returns a functional option for
// NewRegistry. It creates a cached blob statter for use by the // NewRegistry. It creates a cached blob statter for use by the
// registry. // registry.
@ -85,8 +106,9 @@ 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 {

View file

@ -25,10 +25,17 @@ var _ ManifestHandler = &signedManifestHandler{}
func (ms *signedManifestHandler) Unmarshal(ctx context.Context, dgst digest.Digest, content []byte) (distribution.Manifest, error) { func (ms *signedManifestHandler) Unmarshal(ctx context.Context, dgst digest.Digest, content []byte) (distribution.Manifest, error) {
context.GetLogger(ms.ctx).Debug("(*signedManifestHandler).Unmarshal") context.GetLogger(ms.ctx).Debug("(*signedManifestHandler).Unmarshal")
// Fetch the signatures for the manifest
signatures, err := ms.signatures.Get(dgst) var (
if err != nil { signatures [][]byte
return nil, err 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...)
@ -36,6 +43,14 @@ func (ms *signedManifestHandler) Unmarshal(ctx context.Context, dgst digest.Dige
return nil, err return nil, err
} }
if ms.repository.schema1SigningKey != nil {
if err := jsig.Sign(ms.repository.schema1SigningKey); err != nil {
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
raw, err := jsig.PrettySignature("signatures") raw, err := jsig.PrettySignature("signatures")
if err != nil { if err != nil {
@ -75,14 +90,16 @@ func (ms *signedManifestHandler) Put(ctx context.Context, manifest distribution.
return "", err return "", err
} }
// Grab each json signature and store them. if ms.repository.schema1SignaturesEnabled {
signatures, err := sm.Signatures() // Grab each json signature and store them.
if err != nil { signatures, err := sm.Signatures()
return "", err if err != nil {
} return "", err
}
if err := ms.signatures.Put(revision.Digest, signatures...); err != nil { if err := ms.signatures.Put(revision.Digest, signatures...); err != nil {
return "", err return "", err
}
} }
return revision.Digest, nil return revision.Digest, nil