disable schema1 by default, add a config flag to enable it
port of #2473 Signed-off-by: Viktor Stanchev <me@viktorstanchev.com>master
							parent
							
								
									f411848591
								
							
						
					
					
						commit
						e9864ce8b9
					
				|  | @ -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"` | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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/" | ||||||
|  |  | ||||||
|  | @ -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/" | ||||||
|  |  | ||||||
|  | @ -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/" | ||||||
|  |  | ||||||
|  | @ -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` | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -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) | ||||||
|  |  | ||||||
|  | @ -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 { | ||||||
|  |  | ||||||
|  | @ -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) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -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) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -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") | ||||||
|  |  | ||||||
|  | @ -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 { | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
| 		ctx:        ctx, | 	if repo.schema1Enabled { | ||||||
| 		repository: repo, | 		v1Handler = &signedManifestHandler{ | ||||||
| 		blobStore:  blobStore, |  | ||||||
| 		schema1Handler: &signedManifestHandler{ |  | ||||||
| 			ctx:               ctx, | 			ctx:               ctx, | ||||||
| 			schema1SigningKey: repo.schema1SigningKey, | 			schema1SigningKey: repo.schema1SigningKey, | ||||||
| 			repository:        repo, | 			repository:        repo, | ||||||
| 			blobStore:         blobStore, | 			blobStore:         blobStore, | ||||||
| 		}, | 		} | ||||||
|  | 	} else { | ||||||
|  | 		v1Handler = &v1UnsupportedHandler{ | ||||||
|  | 			innerHandler: &signedManifestHandler{ | ||||||
|  | 				ctx:               ctx, | ||||||
|  | 				schema1SigningKey: repo.schema1SigningKey, | ||||||
|  | 				repository:        repo, | ||||||
|  | 				blobStore:         blobStore, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ms := &manifestStore{ | ||||||
|  | 		ctx:            ctx, | ||||||
|  | 		repository:     repo, | ||||||
|  | 		blobStore:      blobStore, | ||||||
|  | 		schema1Handler: v1Handler, | ||||||
| 		schema2Handler: &schema2ManifestHandler{ | 		schema2Handler: &schema2ManifestHandler{ | ||||||
| 			ctx:          ctx, | 			ctx:          ctx, | ||||||
| 			repository:   repo, | 			repository:   repo, | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue