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:
parent
f411848591
commit
e9864ce8b9
15 changed files with 103 additions and 14 deletions
|
@ -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
|
||||||
|
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,
|
||||||
|
|
23
registry/storage/v1unsupportedhandler.go
Normal file
23
registry/storage/v1unsupportedhandler.go
Normal 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
|
||||||
|
}
|
Loading…
Reference in a new issue