disable schema1 by default, add a config flag to enable it

port of #2473

Signed-off-by: Viktor Stanchev <me@viktorstanchev.com>
This commit is contained in:
Viktor Stanchev 2017-12-18 15:06:04 -08:00
parent f411848591
commit e9864ce8b9
15 changed files with 103 additions and 14 deletions

View file

@ -184,6 +184,8 @@ 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"`
// Enabled determines if schema1 manifests should be pullable
Enabled bool `yaml:"enabled,omitempty"`
} `yaml:"schema1,omitempty"` } `yaml:"schema1,omitempty"`
} `yaml:"compatibility,omitempty"` } `yaml:"compatibility,omitempty"`

View file

@ -7,6 +7,9 @@ storage:
rootdirectory: /tmp/registry-dev rootdirectory: /tmp/registry-dev
http: http:
addr: 0.0.0.0:5000 addr: 0.0.0.0:5000
compatibility:
schema1:
enabled: true
auth: auth:
token: token:
realm: "https://auth.localregistry:5559/token/" realm: "https://auth.localregistry:5559/token/"

View file

@ -10,6 +10,9 @@ http:
tls: tls:
certificate: "/etc/docker/registry/localregistry.cert" certificate: "/etc/docker/registry/localregistry.cert"
key: "/etc/docker/registry/localregistry.key" key: "/etc/docker/registry/localregistry.key"
compatibility:
schema1:
enabled: true
auth: auth:
token: token:
realm: "https://auth.localregistry:5559/token/" realm: "https://auth.localregistry:5559/token/"

View file

@ -10,6 +10,9 @@ http:
tls: tls:
certificate: "/etc/docker/registry/localregistry.cert" certificate: "/etc/docker/registry/localregistry.cert"
key: "/etc/docker/registry/localregistry.key" key: "/etc/docker/registry/localregistry.key"
compatibility:
schema1:
enabled: true
auth: auth:
token: token:
realm: "https://auth.localregistry:5556/token/" realm: "https://auth.localregistry:5556/token/"

View file

@ -271,6 +271,7 @@ proxy:
compatibility: compatibility:
schema1: schema1:
signingkeyfile: /etc/registry/key.json signingkeyfile: /etc/registry/key.json
enabled: true
validation: validation:
manifests: manifests:
urls: urls:
@ -1028,6 +1029,7 @@ username (such as `batman`) and the password for that username.
compatibility: compatibility:
schema1: schema1:
signingkeyfile: /etc/registry/key.json signingkeyfile: /etc/registry/key.json
enabled: true
``` ```
Use the `compatibility` structure to configure handling of older and deprecated Use the `compatibility` structure to configure handling of older and deprecated
@ -1038,6 +1040,7 @@ features. Each subsection defines such a feature with configurable behavior.
| Parameter | Required | Description | | Parameter | Required | Description |
|-----------|----------|-------------------------------------------------------| |-----------|----------|-------------------------------------------------------|
| `signingkeyfile` | no | The signing private key used to add signatures to `schema1` manifests. If no signing key is provided, a new ECDSA key is generated when the registry starts. | | `signingkeyfile` | no | The signing private key used to add signatures to `schema1` manifests. If no signing key is provided, a new ECDSA key is generated when the registry starts. |
| `enabled` | no | If this is not set to true, `schema1` manifests cannot be pushed. |
## `validation` ## `validation`

View file

@ -20,6 +20,10 @@ var ErrManifestNotModified = errors.New("manifest not modified")
// performed // performed
var ErrUnsupported = errors.New("operation unsupported") var ErrUnsupported = errors.New("operation unsupported")
// ErrSchemaV1Unsupported is returned when a client tries to upload a schema v1
// manifest but the registry is configured to reject it
var ErrSchemaV1Unsupported = errors.New("manifest schema v1 unsupported")
// ErrTagUnknown is returned if the given tag is not known by the tag service // ErrTagUnknown is returned if the given tag is not known by the tag service
type ErrTagUnknown struct { type ErrTagUnknown struct {
Tag string Tag string

View file

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

View file

@ -2027,6 +2027,7 @@ func newTestEnvMirror(t *testing.T, deleteEnabled bool) *testEnv {
RemoteURL: "http://example.com", RemoteURL: "http://example.com",
}, },
} }
config.Compatibility.Schema1.Enabled = true
return newTestEnvWithConfig(t, &config) return newTestEnvWithConfig(t, &config)
@ -2043,6 +2044,7 @@ func newTestEnv(t *testing.T, deleteEnabled bool) *testEnv {
}, },
} }
config.Compatibility.Schema1.Enabled = true
config.HTTP.Headers = headerConfig config.HTTP.Headers = headerConfig
return newTestEnvWithConfig(t, &config) return newTestEnvWithConfig(t, &config)
@ -2565,6 +2567,7 @@ func TestProxyManifestGetByTag(t *testing.T) {
}}, }},
}, },
} }
truthConfig.Compatibility.Schema1.Enabled = true
truthConfig.HTTP.Headers = headerConfig truthConfig.HTTP.Headers = headerConfig
imageName, _ := reference.WithName("foo/bar") imageName, _ := reference.WithName("foo/bar")
@ -2583,6 +2586,7 @@ func TestProxyManifestGetByTag(t *testing.T) {
RemoteURL: truthEnv.server.URL, RemoteURL: truthEnv.server.URL,
}, },
} }
proxyConfig.Compatibility.Schema1.Enabled = true
proxyConfig.HTTP.Headers = headerConfig proxyConfig.HTTP.Headers = headerConfig
proxyEnv := newTestEnvWithConfig(t, &proxyConfig) proxyEnv := newTestEnvWithConfig(t, &proxyConfig)

View file

@ -174,6 +174,10 @@ func NewApp(ctx context.Context, config *configuration.Configuration) *App {
options = append(options, storage.Schema1SigningKey(app.trustKey)) options = append(options, storage.Schema1SigningKey(app.trustKey))
if config.Compatibility.Schema1.Enabled {
options = append(options, storage.EnableSchema1)
}
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 {

View file

@ -95,7 +95,8 @@ func newManifestStoreTestEnv(t *testing.T, name, tag string) *manifestStoreTestE
ctx := context.Background() ctx := context.Background()
truthRegistry, err := storage.NewRegistry(ctx, inmemory.New(), truthRegistry, err := storage.NewRegistry(ctx, inmemory.New(),
storage.BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), storage.BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()),
storage.Schema1SigningKey(k)) storage.Schema1SigningKey(k),
storage.EnableSchema1)
if err != nil { if err != nil {
t.Fatalf("error creating registry: %v", err) t.Fatalf("error creating registry: %v", err)
} }
@ -117,7 +118,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, storage.Schema1SigningKey(k)) localRegistry, err := storage.NewRegistry(ctx, inmemory.New(), storage.BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), storage.EnableRedirect, storage.DisableDigestResumption, storage.Schema1SigningKey(k), storage.EnableSchema1)
if err != nil { if err != nil {
t.Fatalf("error creating registry: %v", err) t.Fatalf("error creating registry: %v", err)
} }

View file

@ -26,7 +26,7 @@ type setupEnv struct {
func setupFS(t *testing.T) *setupEnv { func setupFS(t *testing.T) *setupEnv {
d := inmemory.New() d := inmemory.New()
ctx := context.Background() ctx := context.Background()
registry, err := NewRegistry(ctx, d, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableRedirect) registry, err := NewRegistry(ctx, d, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableRedirect, EnableSchema1)
if err != nil { if err != nil {
t.Fatalf("error creating registry: %v", err) t.Fatalf("error creating registry: %v", err)
} }
@ -207,7 +207,7 @@ func testEq(a, b []string, size int) bool {
func setupBadWalkEnv(t *testing.T) *setupEnv { func setupBadWalkEnv(t *testing.T) *setupEnv {
d := newBadListDriver() d := newBadListDriver()
ctx := context.Background() ctx := context.Background()
registry, err := NewRegistry(ctx, d, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableRedirect) registry, err := NewRegistry(ctx, d, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableRedirect, EnableSchema1)
if err != nil { if err != nil {
t.Fatalf("error creating registry: %v", err) t.Fatalf("error creating registry: %v", err)
} }

View file

@ -27,7 +27,7 @@ func createRegistry(t *testing.T, driver driver.StorageDriver, options ...Regist
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
options = append([]RegistryOption{EnableDelete, Schema1SigningKey(k)}, options...) options = append([]RegistryOption{EnableDelete, Schema1SigningKey(k), EnableSchema1}, options...)
registry, err := NewRegistry(ctx, driver, options...) registry, err := NewRegistry(ctx, driver, options...)
if err != nil { if err != nil {
t.Fatalf("Failed to construct namespace") t.Fatalf("Failed to construct namespace")

View file

@ -56,10 +56,18 @@ func TestManifestStorage(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
testManifestStorage(t, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableDelete, EnableRedirect, Schema1SigningKey(k)) testManifestStorage(t, true, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableDelete, EnableRedirect, Schema1SigningKey(k), EnableSchema1)
} }
func testManifestStorage(t *testing.T, options ...RegistryOption) { func TestManifestStorageV1Unsupported(t *testing.T) {
k, err := libtrust.GenerateECP256PrivateKey()
if err != nil {
t.Fatal(err)
}
testManifestStorage(t, false, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableDelete, EnableRedirect, Schema1SigningKey(k))
}
func testManifestStorage(t *testing.T, schema1Enabled bool, options ...RegistryOption) {
repoName, _ := reference.WithName("foo/bar") repoName, _ := reference.WithName("foo/bar")
env := newManifestStoreTestEnv(t, repoName, "thetag", options...) env := newManifestStoreTestEnv(t, repoName, "thetag", options...)
ctx := context.Background() ctx := context.Background()
@ -111,6 +119,15 @@ func testManifestStorage(t *testing.T, options ...RegistryOption) {
t.Fatalf("expected errors putting manifest with full verification") t.Fatalf("expected errors putting manifest with full verification")
} }
// If schema1 is not enabled, do a short version of this test, just checking
// if we get the right error when we Put
if !schema1Enabled {
if err != distribution.ErrSchemaV1Unsupported {
t.Fatalf("got the wrong error when schema1 is disabled: %s", err)
}
return
}
switch err := err.(type) { switch err := err.(type) {
case distribution.ErrManifestVerification: case distribution.ErrManifestVerification:
if len(err) != 2 { if len(err) != 2 {

View file

@ -19,6 +19,7 @@ type registry struct {
statter *blobStatter // global statter service. statter *blobStatter // global statter service.
blobDescriptorCacheProvider cache.BlobDescriptorCacheProvider blobDescriptorCacheProvider cache.BlobDescriptorCacheProvider
deleteEnabled bool deleteEnabled bool
schema1Enabled bool
resumableDigestEnabled bool resumableDigestEnabled bool
schema1SigningKey libtrust.PrivateKey schema1SigningKey libtrust.PrivateKey
blobDescriptorServiceFactory distribution.BlobDescriptorServiceFactory blobDescriptorServiceFactory distribution.BlobDescriptorServiceFactory
@ -48,6 +49,13 @@ func EnableDelete(registry *registry) error {
return nil return nil
} }
// EnableSchema1 is a functional option for NewRegistry. It enables pushing of
// schema1 manifests.
func EnableSchema1(registry *registry) error {
registry.schema1Enabled = true
return nil
}
// DisableDigestResumption is a functional option for NewRegistry. It should be // DisableDigestResumption is a functional option for NewRegistry. It should be
// used if the registry is acting as a caching proxy. // used if the registry is acting as a caching proxy.
func DisableDigestResumption(registry *registry) error { func DisableDigestResumption(registry *registry) error {
@ -237,16 +245,30 @@ func (repo *repository) Manifests(ctx context.Context, options ...distribution.M
linkDirectoryPathSpec: manifestDirectoryPathSpec, linkDirectoryPathSpec: manifestDirectoryPathSpec,
} }
ms := &manifestStore{ var v1Handler ManifestHandler
if repo.schema1Enabled {
v1Handler = &signedManifestHandler{
ctx: ctx, ctx: ctx,
schema1SigningKey: repo.schema1SigningKey,
repository: repo, repository: repo,
blobStore: blobStore, blobStore: blobStore,
schema1Handler: &signedManifestHandler{ }
} else {
v1Handler = &v1UnsupportedHandler{
innerHandler: &signedManifestHandler{
ctx: ctx, ctx: ctx,
schema1SigningKey: repo.schema1SigningKey, schema1SigningKey: repo.schema1SigningKey,
repository: repo, repository: repo,
blobStore: blobStore, blobStore: blobStore,
}, },
}
}
ms := &manifestStore{
ctx: ctx,
repository: repo,
blobStore: blobStore,
schema1Handler: v1Handler,
schema2Handler: &schema2ManifestHandler{ schema2Handler: &schema2ManifestHandler{
ctx: ctx, ctx: ctx,
repository: repo, repository: repo,

View file

@ -0,0 +1,23 @@
package storage
import (
"context"
"github.com/docker/distribution"
digest "github.com/opencontainers/go-digest"
)
// signedManifestHandler is a ManifestHandler that unmarshals v1 manifests but
// refuses to Put v1 manifests
type v1UnsupportedHandler struct {
innerHandler ManifestHandler
}
var _ ManifestHandler = &v1UnsupportedHandler{}
func (v *v1UnsupportedHandler) Unmarshal(ctx context.Context, dgst digest.Digest, content []byte) (distribution.Manifest, error) {
return v.innerHandler.Unmarshal(ctx, dgst, content)
}
func (v *v1UnsupportedHandler) Put(ctx context.Context, manifest distribution.Manifest, skipDependencyVerification bool) (digest.Digest, error) {
return digest.Digest(""), distribution.ErrSchemaV1Unsupported
}