From 62b33a2604c0bacdd26bfbe7303cb6e2ed26d432 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 27 Feb 2024 17:30:46 +0100 Subject: [PATCH] disable pulling legacy image formats by default This patch disables pulling legacy (schema1 and schema 2, version 1) images by default. A `DOCKER_ENABLE_DEPRECATED_PULL_SCHEMA_1_IMAGE` environment-variable is introduced to allow re-enabling this feature, aligning with the environment variable used in containerd 2.0 (`CONTAINERD_ENABLE_DEPRECATED_PULL_SCHEMA_1_IMAGE`). With this patch, attempts to pull a legacy image produces an error: With graphdrivers: docker pull docker:1.0 1.0: Pulling from library/docker [DEPRECATION NOTICE] Docker Image Format v1, and Docker Image manifest version 2, schema 1 support will be removed in an upcoming release. Suggest the author of docker.io/library/docker:1.0 to upgrade the image to the OCI Format, or Docker Image manifest v2, schema 2. More information at https://docs.docker.com/go/deprecated-image-specs/ With the containerd image store enabled, output is slightly different as it returns the error before printing the `1.0: pulling ...`: docker pull docker:1.0 Error response from daemon: [DEPRECATION NOTICE] Docker Image Format v1 and Docker Image manifest version 2, schema 1 support is disabled by default and will be removed in an upcoming release. Suggest the author of docker.io/library/docker:1.0 to upgrade the image to the OCI Format or Docker Image manifest v2, schema 2. More information at https://docs.docker.com/go/deprecated-image-specs/ Using the "distribution" endpoint to resolve the digest for an image also produces an error: curl -v --unix-socket /var/run/docker.sock http://foo/distribution/docker.io/library/docker:1.0/json * Trying /var/run/docker.sock:0... * Connected to foo (/var/run/docker.sock) port 80 (#0) > GET /distribution/docker.io/library/docker:1.0/json HTTP/1.1 > Host: foo > User-Agent: curl/7.88.1 > Accept: */* > < HTTP/1.1 400 Bad Request < Api-Version: 1.45 < Content-Type: application/json < Docker-Experimental: false < Ostype: linux < Server: Docker/dev (linux) < Date: Tue, 27 Feb 2024 16:09:42 GMT < Content-Length: 354 < {"message":"[DEPRECATION NOTICE] Docker Image Format v1, and Docker Image manifest version 2, schema 1 support will be removed in an upcoming release. Suggest the author of docker.io/library/docker:1.0 to upgrade the image to the OCI Format, or Docker Image manifest v2, schema 2. More information at https://docs.docker.com/go/deprecated-image-specs/"} * Connection #0 to host foo left intact Starting the daemon with the `DOCKER_ENABLE_DEPRECATED_PULL_SCHEMA_1_IMAGE` env-var set to a non-empty value allows pulling the image; docker pull docker:1.0 [DEPRECATION NOTICE] Docker Image Format v1 and Docker Image manifest version 2, schema 1 support is disabled by default and will be removed in an upcoming release. Suggest the author of docker.io/library/docker:1.0 to upgrade the image to the OCI Format or Docker Image manifest v2, schema 2. More information at https://docs.docker.com/go/deprecated-image-specs/ b0a0e6710d13: Already exists d193ad713811: Already exists ba7268c3149b: Already exists c862d82a67a2: Already exists Digest: sha256:5e7081837926c7a40e58881bbebc52044a95a62a2ea52fb240db3fc539212fe5 Status: Image is up to date for docker:1.0 docker.io/library/docker:1.0 Signed-off-by: Sebastiaan van Stijn --- .../router/distribution/distribution_routes.go | 5 +++++ daemon/containerd/image_pull.go | 8 +++++++- distribution/errors.go | 13 +++++++++++-- distribution/manifest.go | 11 +++++++++++ distribution/manifest_test.go | 2 ++ distribution/pull_v2.go | 18 ++++++++++++------ hack/make/.integration-daemon-start | 3 +++ 7 files changed, 51 insertions(+), 9 deletions(-) diff --git a/api/server/router/distribution/distribution_routes.go b/api/server/router/distribution/distribution_routes.go index 7b9d128436..d07e24cf5b 100644 --- a/api/server/router/distribution/distribution_routes.go +++ b/api/server/router/distribution/distribution_routes.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "net/http" + "os" "github.com/distribution/reference" "github.com/docker/distribution" @@ -12,6 +13,7 @@ import ( "github.com/docker/distribution/manifest/schema2" "github.com/docker/docker/api/server/httputils" "github.com/docker/docker/api/types/registry" + distributionpkg "github.com/docker/docker/distribution" "github.com/docker/docker/errdefs" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" @@ -153,6 +155,9 @@ func (s *distributionRouter) fetchManifest(ctx context.Context, distrepo distrib } } case *schema1.SignedManifest: + if os.Getenv("DOCKER_ENABLE_DEPRECATED_PULL_SCHEMA_1_IMAGE") == "" { + return registry.DistributionInspect{}, distributionpkg.DeprecatedSchema1ImageError(namedRef) + } platform := ocispec.Platform{ Architecture: mnfstObj.Architecture, OS: "linux", diff --git a/daemon/containerd/image_pull.go b/daemon/containerd/image_pull.go index 88ef659f2d..26b27263cc 100644 --- a/daemon/containerd/image_pull.go +++ b/daemon/containerd/image_pull.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io" + "os" "strings" "github.com/containerd/containerd" @@ -115,7 +116,12 @@ func (i *ImageService) pullTag(ctx context.Context, ref reference.Named, platfor var sentPullingFrom, sentSchema1Deprecation bool ah := images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { if desc.MediaType == images.MediaTypeDockerSchema1Manifest && !sentSchema1Deprecation { - progress.Message(out, "", distribution.DeprecatedSchema1ImageMessage(ref)) + err := distribution.DeprecatedSchema1ImageError(ref) + if os.Getenv("DOCKER_ENABLE_DEPRECATED_PULL_SCHEMA_1_IMAGE") == "" { + log.G(context.TODO()).Warn(err.Error()) + return nil, err + } + progress.Message(out, "", err.Error()) sentSchema1Deprecation = true } if images.IsLayerType(desc.MediaType) { diff --git a/distribution/errors.go b/distribution/errors.go index 24315d4485..ba213f15b5 100644 --- a/distribution/errors.go +++ b/distribution/errors.go @@ -213,6 +213,15 @@ func (e reservedNameError) Error() string { func (e reservedNameError) Forbidden() {} -func DeprecatedSchema1ImageMessage(ref reference.Named) string { - return fmt.Sprintf("[DEPRECATION NOTICE] Docker Image Format v1, and Docker Image manifest version 2, schema 1 support will be removed in an upcoming release. Suggest the author of %s to upgrade the image to the OCI Format, or Docker Image manifest v2, schema 2. More information at https://docs.docker.com/go/deprecated-image-specs/", ref) +type invalidArgumentErr struct{ error } + +func (invalidArgumentErr) InvalidParameter() {} + +func DeprecatedSchema1ImageError(ref reference.Named) error { + msg := "[DEPRECATION NOTICE] Docker Image Format v1 and Docker Image manifest version 2, schema 1 support is disabled by default and will be removed in an upcoming release." + if ref != nil { + msg += " Suggest the author of " + ref.String() + " to upgrade the image to the OCI Format or Docker Image manifest v2, schema 2." + } + msg += " More information at https://docs.docker.com/go/deprecated-image-specs/" + return invalidArgumentErr{errors.New(msg)} } diff --git a/distribution/manifest.go b/distribution/manifest.go index 2e91b40d65..f68fb24fa1 100644 --- a/distribution/manifest.go +++ b/distribution/manifest.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io" + "os" "strings" "github.com/containerd/containerd/content" @@ -292,6 +293,11 @@ func detectManifestBlobMediaType(dt []byte) (string, error) { } return mfst.MediaType, nil case schema1.MediaTypeManifest: + if os.Getenv("DOCKER_ENABLE_DEPRECATED_PULL_SCHEMA_1_IMAGE") == "" { + err := DeprecatedSchema1ImageError(nil) + log.G(context.TODO()).Warn(err.Error()) + return "", err + } if mfst.Manifests != nil || mfst.Layers != nil { return "", fmt.Errorf(`media-type: %q should not have "manifests" or "layers"`, mfst.MediaType) } @@ -303,6 +309,11 @@ func detectManifestBlobMediaType(dt []byte) (string, error) { } switch { case mfst.FSLayers != nil && mfst.Manifests == nil && mfst.Layers == nil && mfst.Config == nil: + if os.Getenv("DOCKER_ENABLE_DEPRECATED_PULL_SCHEMA_1_IMAGE") == "" { + err := DeprecatedSchema1ImageError(nil) + log.G(context.TODO()).Warn(err.Error()) + return "", err + } return schema1.MediaTypeManifest, nil case mfst.Config != nil && mfst.Manifests == nil && mfst.FSLayers == nil, mfst.Layers != nil && mfst.Manifests == nil && mfst.FSLayers == nil: diff --git a/distribution/manifest_test.go b/distribution/manifest_test.go index e103ef7fd3..a534d501d8 100644 --- a/distribution/manifest_test.go +++ b/distribution/manifest_test.go @@ -362,6 +362,7 @@ func TestDetectManifestBlobMediaType(t *testing.T) { "mediaType and fsLayers set": {[]byte(`{"mediaType": "bananas", "fsLayers": []}`), "bananas"}, } + t.Setenv("DOCKER_ENABLE_DEPRECATED_PULL_SCHEMA_1_IMAGE", "1") for name, tc := range cases { t.Run(name, func(t *testing.T) { mt, err := detectManifestBlobMediaType(tc.json) @@ -431,6 +432,7 @@ func TestDetectManifestBlobMediaTypeInvalid(t *testing.T) { }, } + t.Setenv("DOCKER_ENABLE_DEPRECATED_PULL_SCHEMA_1_IMAGE", "1") for name, tc := range cases { t.Run(name, func(t *testing.T) { mt, err := detectManifestBlobMediaType(tc.json) diff --git a/distribution/pull_v2.go b/distribution/pull_v2.go index da3d8a8edf..2defc31d10 100644 --- a/distribution/pull_v2.go +++ b/distribution/pull_v2.go @@ -424,9 +424,12 @@ func (p *puller) pullTag(ctx context.Context, ref reference.Named, platform *oci switch v := manifest.(type) { case *schema1.SignedManifest: - msg := DeprecatedSchema1ImageMessage(ref) - log.G(ctx).Warn(msg) - progress.Message(p.config.ProgressOutput, "", msg) + err := DeprecatedSchema1ImageError(ref) + log.G(ctx).Warn(err.Error()) + if os.Getenv("DOCKER_ENABLE_DEPRECATED_PULL_SCHEMA_1_IMAGE") == "" { + return false, err + } + progress.Message(p.config.ProgressOutput, "", err.Error()) id, manifestDigest, err = p.pullSchema1(ctx, ref, v, platform) if err != nil { @@ -857,9 +860,12 @@ func (p *puller) pullManifestList(ctx context.Context, ref reference.Named, mfst switch v := manifest.(type) { case *schema1.SignedManifest: - msg := DeprecatedSchema1ImageMessage(ref) - log.G(ctx).Warn(msg) - progress.Message(p.config.ProgressOutput, "", msg) + err := DeprecatedSchema1ImageError(ref) + log.G(ctx).Warn(err.Error()) + if os.Getenv("DOCKER_ENABLE_DEPRECATED_PULL_SCHEMA_1_IMAGE") == "" { + return "", "", err + } + progress.Message(p.config.ProgressOutput, "", err.Error()) platform := toOCIPlatform(match.Platform) id, _, err = p.pullSchema1(ctx, manifestRef, v, platform) diff --git a/hack/make/.integration-daemon-start b/hack/make/.integration-daemon-start index cf8002afba..9d3d04817f 100644 --- a/hack/make/.integration-daemon-start +++ b/hack/make/.integration-daemon-start @@ -46,6 +46,9 @@ export DOCKER_ALLOW_SCHEMA1_PUSH_DONOTUSE=1 export DOCKER_GRAPHDRIVER=${DOCKER_GRAPHDRIVER:-vfs} export DOCKER_USERLANDPROXY=${DOCKER_USERLANDPROXY:-true} +# Allow testing push/pull of legacy image formats +export DOCKER_ENABLE_DEPRECATED_PULL_SCHEMA_1_IMAGE=1 + # example usage: DOCKER_STORAGE_OPTS="dm.basesize=20G,dm.loopdatasize=200G" storage_params="" if [ -n "$DOCKER_STORAGE_OPTS" ]; then