Add option to disable signatures
Add option for specifying trust key for signing schema1 manifests. Since schema1 signature key identifiers are not verified anywhere and deprecated, storing signatures is no longer a requirement. Furthermore in schema2 there is no signature, requiring the registry to already add signatures to generated schema1 manifests. Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
This commit is contained in:
parent
c4b79bda8a
commit
956ece5c70
4 changed files with 115 additions and 33 deletions
|
@ -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"]
|
||||||
|
|
|
@ -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]))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue