From 1fa2e3115105f6b2334f452bd08415e8f00633d7 Mon Sep 17 00:00:00 2001 From: Aaron Lehmann Date: Fri, 18 Dec 2015 15:06:23 -0800 Subject: [PATCH] Build a pre-schema2 registry to test schema1 push/pull Add DockerSchema1RegistrySuite which uses this registry, and make applicable integration tests run as part of this suite. Signed-off-by: Aaron Lehmann --- Dockerfile | 11 +- integration-cli/check_test.go | 32 ++++- integration-cli/docker_cli_by_digest_test.go | 116 +++++++++++++++++- integration-cli/docker_cli_pull_local_test.go | 60 +++++++-- integration-cli/docker_cli_push_test.go | 50 +++++++- integration-cli/docker_utils.go | 4 +- integration-cli/registry.go | 13 +- 7 files changed, 259 insertions(+), 27 deletions(-) diff --git a/Dockerfile b/Dockerfile index 78a986b3db..35489d4415 100644 --- a/Dockerfile +++ b/Dockerfile @@ -142,14 +142,21 @@ RUN set -x \ ) \ && rm -rf "$SECCOMP_PATH" -# Install registry -ENV REGISTRY_COMMIT ec87e9b6971d831f0eff752ddb54fb64693e51cd +# Install two versions of the registry. The first is an older version that +# only supports schema1 manifests. The second is a newer version that supports +# both. This allows integration-cli tests to cover push/pull with both schema1 +# and schema2 manifests. +ENV REGISTRY_COMMIT_SCHEMA1 ec87e9b6971d831f0eff752ddb54fb64693e51cd +ENV REGISTRY_COMMIT a7ae88da459b98b481a245e5b1750134724ac67d RUN set -x \ && export GOPATH="$(mktemp -d)" \ && git clone https://github.com/docker/distribution.git "$GOPATH/src/github.com/docker/distribution" \ && (cd "$GOPATH/src/github.com/docker/distribution" && git checkout -q "$REGISTRY_COMMIT") \ && GOPATH="$GOPATH/src/github.com/docker/distribution/Godeps/_workspace:$GOPATH" \ go build -o /usr/local/bin/registry-v2 github.com/docker/distribution/cmd/registry \ + && (cd "$GOPATH/src/github.com/docker/distribution" && git checkout -q "$REGISTRY_COMMIT_SCHEMA1") \ + && GOPATH="$GOPATH/src/github.com/docker/distribution/Godeps/_workspace:$GOPATH" \ + go build -o /usr/local/bin/registry-v2-schema1 github.com/docker/distribution/cmd/registry \ && rm -rf "$GOPATH" # Install notary server diff --git a/integration-cli/check_test.go b/integration-cli/check_test.go index 030b07f0e5..29ae8b86ce 100644 --- a/integration-cli/check_test.go +++ b/integration-cli/check_test.go @@ -48,7 +48,7 @@ type DockerRegistrySuite struct { func (s *DockerRegistrySuite) SetUpTest(c *check.C) { testRequires(c, DaemonIsLinux) - s.reg = setupRegistry(c) + s.reg = setupRegistry(c, false) s.d = NewDaemon(c) } @@ -62,6 +62,34 @@ func (s *DockerRegistrySuite) TearDownTest(c *check.C) { s.d.Stop() } +func init() { + check.Suite(&DockerSchema1RegistrySuite{ + ds: &DockerSuite{}, + }) +} + +type DockerSchema1RegistrySuite struct { + ds *DockerSuite + reg *testRegistryV2 + d *Daemon +} + +func (s *DockerSchema1RegistrySuite) SetUpTest(c *check.C) { + testRequires(c, DaemonIsLinux) + s.reg = setupRegistry(c, true) + s.d = NewDaemon(c) +} + +func (s *DockerSchema1RegistrySuite) TearDownTest(c *check.C) { + if s.reg != nil { + s.reg.Close() + } + if s.ds != nil { + s.ds.TearDownTest(c) + } + s.d.Stop() +} + func init() { check.Suite(&DockerDaemonSuite{ ds: &DockerSuite{}, @@ -97,7 +125,7 @@ type DockerTrustSuite struct { } func (s *DockerTrustSuite) SetUpTest(c *check.C) { - s.reg = setupRegistry(c) + s.reg = setupRegistry(c, false) s.not = setupNotary(c) } diff --git a/integration-cli/docker_cli_by_digest_test.go b/integration-cli/docker_cli_by_digest_test.go index 446a28eadf..7c043a1ce8 100644 --- a/integration-cli/docker_cli_by_digest_test.go +++ b/integration-cli/docker_cli_by_digest_test.go @@ -10,6 +10,7 @@ import ( "github.com/docker/distribution/digest" "github.com/docker/distribution/manifest/schema1" + "github.com/docker/distribution/manifest/schema2" "github.com/docker/docker/pkg/integration/checker" "github.com/docker/docker/pkg/stringutils" "github.com/docker/engine-api/types" @@ -56,7 +57,7 @@ func setupImageWithTag(c *check.C, tag string) (digest.Digest, error) { return digest.Digest(pushDigest), nil } -func (s *DockerRegistrySuite) TestPullByTagDisplaysDigest(c *check.C) { +func testPullByTagDisplaysDigest(c *check.C) { testRequires(c, DaemonIsLinux) pushDigest, err := setupImage(c) c.Assert(err, checker.IsNil, check.Commentf("error setting up image")) @@ -73,7 +74,15 @@ func (s *DockerRegistrySuite) TestPullByTagDisplaysDigest(c *check.C) { c.Assert(pushDigest.String(), checker.Equals, pullDigest) } -func (s *DockerRegistrySuite) TestPullByDigest(c *check.C) { +func (s *DockerRegistrySuite) TestPullByTagDisplaysDigest(c *check.C) { + testPullByTagDisplaysDigest(c) +} + +func (s *DockerSchema1RegistrySuite) TestPullByTagDisplaysDigest(c *check.C) { + testPullByTagDisplaysDigest(c) +} + +func testPullByDigest(c *check.C) { testRequires(c, DaemonIsLinux) pushDigest, err := setupImage(c) c.Assert(err, checker.IsNil, check.Commentf("error setting up image")) @@ -91,7 +100,15 @@ func (s *DockerRegistrySuite) TestPullByDigest(c *check.C) { c.Assert(pushDigest.String(), checker.Equals, pullDigest) } -func (s *DockerRegistrySuite) TestPullByDigestNoFallback(c *check.C) { +func (s *DockerRegistrySuite) TestPullByDigest(c *check.C) { + testPullByDigest(c) +} + +func (s *DockerSchema1RegistrySuite) TestPullByDigest(c *check.C) { + testPullByDigest(c) +} + +func testPullByDigestNoFallback(c *check.C) { testRequires(c, DaemonIsLinux) // pull from the registry using the @ reference imageReference := fmt.Sprintf("%s@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", repoName) @@ -100,6 +117,14 @@ func (s *DockerRegistrySuite) TestPullByDigestNoFallback(c *check.C) { c.Assert(out, checker.Contains, "manifest unknown", check.Commentf("expected non-zero exit status and correct error message when pulling non-existing image")) } +func (s *DockerRegistrySuite) TestPullByDigestNoFallback(c *check.C) { + testPullByDigestNoFallback(c) +} + +func (s *DockerSchema1RegistrySuite) TestPullByDigestNoFallback(c *check.C) { + testPullByDigestNoFallback(c) +} + func (s *DockerRegistrySuite) TestCreateByDigest(c *check.C) { pushDigest, err := setupImage(c) c.Assert(err, checker.IsNil, check.Commentf("error setting up image")) @@ -372,6 +397,7 @@ func (s *DockerRegistrySuite) TestDeleteImageByIDOnlyPulledByDigest(c *check.C) // TestPullFailsWithAlteredManifest tests that a `docker pull` fails when // we have modified a manifest blob and its digest cannot be verified. +// This is the schema2 version of the test. func (s *DockerRegistrySuite) TestPullFailsWithAlteredManifest(c *check.C) { testRequires(c, DaemonIsLinux) manifestDigest, err := setupImage(c) @@ -380,6 +406,46 @@ func (s *DockerRegistrySuite) TestPullFailsWithAlteredManifest(c *check.C) { // Load the target manifest blob. manifestBlob := s.reg.readBlobContents(c, manifestDigest) + var imgManifest schema2.Manifest + err = json.Unmarshal(manifestBlob, &imgManifest) + c.Assert(err, checker.IsNil, check.Commentf("unable to decode image manifest from blob")) + + // Change a layer in the manifest. + imgManifest.Layers[0].Digest = digest.Digest("sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") + + // Move the existing data file aside, so that we can replace it with a + // malicious blob of data. NOTE: we defer the returned undo func. + undo := s.reg.tempMoveBlobData(c, manifestDigest) + defer undo() + + alteredManifestBlob, err := json.MarshalIndent(imgManifest, "", " ") + c.Assert(err, checker.IsNil, check.Commentf("unable to encode altered image manifest to JSON")) + + s.reg.writeBlobContents(c, manifestDigest, alteredManifestBlob) + + // Now try pulling that image by digest. We should get an error about + // digest verification for the manifest digest. + + // Pull from the registry using the @ reference. + imageReference := fmt.Sprintf("%s@%s", repoName, manifestDigest) + out, exitStatus, _ := dockerCmdWithError("pull", imageReference) + c.Assert(exitStatus, checker.Not(check.Equals), 0) + + expectedErrorMsg := fmt.Sprintf("manifest verification failed for digest %s", manifestDigest) + c.Assert(out, checker.Contains, expectedErrorMsg) +} + +// TestPullFailsWithAlteredManifest tests that a `docker pull` fails when +// we have modified a manifest blob and its digest cannot be verified. +// This is the schema1 version of the test. +func (s *DockerSchema1RegistrySuite) TestPullFailsWithAlteredManifest(c *check.C) { + testRequires(c, DaemonIsLinux) + manifestDigest, err := setupImage(c) + c.Assert(err, checker.IsNil, check.Commentf("error setting up image")) + + // Load the target manifest blob. + manifestBlob := s.reg.readBlobContents(c, manifestDigest) + var imgManifest schema1.Manifest err = json.Unmarshal(manifestBlob, &imgManifest) c.Assert(err, checker.IsNil, check.Commentf("unable to decode image manifest from blob")) @@ -413,6 +479,7 @@ func (s *DockerRegistrySuite) TestPullFailsWithAlteredManifest(c *check.C) { // TestPullFailsWithAlteredLayer tests that a `docker pull` fails when // we have modified a layer blob and its digest cannot be verified. +// This is the schema2 version of the test. func (s *DockerRegistrySuite) TestPullFailsWithAlteredLayer(c *check.C) { testRequires(c, DaemonIsLinux) manifestDigest, err := setupImage(c) @@ -421,6 +488,49 @@ func (s *DockerRegistrySuite) TestPullFailsWithAlteredLayer(c *check.C) { // Load the target manifest blob. manifestBlob := s.reg.readBlobContents(c, manifestDigest) + var imgManifest schema2.Manifest + err = json.Unmarshal(manifestBlob, &imgManifest) + c.Assert(err, checker.IsNil) + + // Next, get the digest of one of the layers from the manifest. + targetLayerDigest := imgManifest.Layers[0].Digest + + // Move the existing data file aside, so that we can replace it with a + // malicious blob of data. NOTE: we defer the returned undo func. + undo := s.reg.tempMoveBlobData(c, targetLayerDigest) + defer undo() + + // Now make a fake data blob in this directory. + s.reg.writeBlobContents(c, targetLayerDigest, []byte("This is not the data you are looking for.")) + + // Now try pulling that image by digest. We should get an error about + // digest verification for the target layer digest. + + // Remove distribution cache to force a re-pull of the blobs + if err := os.RemoveAll(filepath.Join(dockerBasePath, "image", s.d.storageDriver, "distribution")); err != nil { + c.Fatalf("error clearing distribution cache: %v", err) + } + + // Pull from the registry using the @ reference. + imageReference := fmt.Sprintf("%s@%s", repoName, manifestDigest) + out, exitStatus, _ := dockerCmdWithError("pull", imageReference) + c.Assert(exitStatus, checker.Not(check.Equals), 0, check.Commentf("expected a zero exit status")) + + expectedErrorMsg := fmt.Sprintf("filesystem layer verification failed for digest %s", targetLayerDigest) + c.Assert(out, checker.Contains, expectedErrorMsg, check.Commentf("expected error message in output: %s", out)) +} + +// TestPullFailsWithAlteredLayer tests that a `docker pull` fails when +// we have modified a layer blob and its digest cannot be verified. +// This is the schema1 version of the test. +func (s *DockerSchema1RegistrySuite) TestPullFailsWithAlteredLayer(c *check.C) { + testRequires(c, DaemonIsLinux) + manifestDigest, err := setupImage(c) + c.Assert(err, checker.IsNil) + + // Load the target manifest blob. + manifestBlob := s.reg.readBlobContents(c, manifestDigest) + var imgManifest schema1.Manifest err = json.Unmarshal(manifestBlob, &imgManifest) c.Assert(err, checker.IsNil) diff --git a/integration-cli/docker_cli_pull_local_test.go b/integration-cli/docker_cli_pull_local_test.go index 8e7da2fa6e..9c4ff35a7c 100644 --- a/integration-cli/docker_cli_pull_local_test.go +++ b/integration-cli/docker_cli_pull_local_test.go @@ -9,11 +9,11 @@ import ( "github.com/go-check/check" ) -// TestPullImageWithAliases pulls a specific image tag and verifies that any aliases (i.e., other +// testPullImageWithAliases pulls a specific image tag and verifies that any aliases (i.e., other // tags for the same image) are not also pulled down. // // Ref: docker/docker#8141 -func (s *DockerRegistrySuite) TestPullImageWithAliases(c *check.C) { +func testPullImageWithAliases(c *check.C) { repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) repos := []string{} @@ -40,8 +40,16 @@ func (s *DockerRegistrySuite) TestPullImageWithAliases(c *check.C) { } } -// TestConcurrentPullWholeRepo pulls the same repo concurrently. -func (s *DockerRegistrySuite) TestConcurrentPullWholeRepo(c *check.C) { +func (s *DockerRegistrySuite) TestPullImageWithAliases(c *check.C) { + testPullImageWithAliases(c) +} + +func (s *DockerSchema1RegistrySuite) TestPullImageWithAliases(c *check.C) { + testPullImageWithAliases(c) +} + +// testConcurrentPullWholeRepo pulls the same repo concurrently. +func testConcurrentPullWholeRepo(c *check.C) { repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) repos := []string{} @@ -89,8 +97,16 @@ func (s *DockerRegistrySuite) TestConcurrentPullWholeRepo(c *check.C) { } } -// TestConcurrentFailingPull tries a concurrent pull that doesn't succeed. -func (s *DockerRegistrySuite) TestConcurrentFailingPull(c *check.C) { +func (s *DockerRegistrySuite) testConcurrentPullWholeRepo(c *check.C) { + testConcurrentPullWholeRepo(c) +} + +func (s *DockerSchema1RegistrySuite) testConcurrentPullWholeRepo(c *check.C) { + testConcurrentPullWholeRepo(c) +} + +// testConcurrentFailingPull tries a concurrent pull that doesn't succeed. +func testConcurrentFailingPull(c *check.C) { repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) // Run multiple pulls concurrently @@ -112,9 +128,17 @@ func (s *DockerRegistrySuite) TestConcurrentFailingPull(c *check.C) { } } -// TestConcurrentPullMultipleTags pulls multiple tags from the same repo +func (s *DockerRegistrySuite) testConcurrentFailingPull(c *check.C) { + testConcurrentFailingPull(c) +} + +func (s *DockerSchema1RegistrySuite) testConcurrentFailingPull(c *check.C) { + testConcurrentFailingPull(c) +} + +// testConcurrentPullMultipleTags pulls multiple tags from the same repo // concurrently. -func (s *DockerRegistrySuite) TestConcurrentPullMultipleTags(c *check.C) { +func testConcurrentPullMultipleTags(c *check.C) { repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) repos := []string{} @@ -161,9 +185,17 @@ func (s *DockerRegistrySuite) TestConcurrentPullMultipleTags(c *check.C) { } } -// TestPullIDStability verifies that pushing an image and pulling it back +func (s *DockerRegistrySuite) TestConcurrentPullMultipleTags(c *check.C) { + testConcurrentPullMultipleTags(c) +} + +func (s *DockerSchema1RegistrySuite) TestConcurrentPullMultipleTags(c *check.C) { + testConcurrentPullMultipleTags(c) +} + +// testPullIDStability verifies that pushing an image and pulling it back // preserves the image ID. -func (s *DockerRegistrySuite) TestPullIDStability(c *check.C) { +func testPullIDStability(c *check.C) { derivedImage := privateRegistryURL + "/dockercli/id-stability" baseImage := "busybox" @@ -229,6 +261,14 @@ func (s *DockerRegistrySuite) TestPullIDStability(c *check.C) { } } +func (s *DockerRegistrySuite) TestPullIDStability(c *check.C) { + testPullIDStability(c) +} + +func (s *DockerSchema1RegistrySuite) TestPullIDStability(c *check.C) { + testPullIDStability(c) +} + // TestPullFallbackOn404 tries to pull a nonexistent manifest and confirms that // the pull falls back to the v1 protocol. // diff --git a/integration-cli/docker_cli_push_test.go b/integration-cli/docker_cli_push_test.go index 333ea035b2..be5f9aad8e 100644 --- a/integration-cli/docker_cli_push_test.go +++ b/integration-cli/docker_cli_push_test.go @@ -16,7 +16,7 @@ import ( ) // Pushing an image to a private registry. -func (s *DockerRegistrySuite) TestPushBusyboxImage(c *check.C) { +func testPushBusyboxImage(c *check.C) { repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) // tag the image to upload it to the private registry dockerCmd(c, "tag", "busybox", repoName) @@ -24,13 +24,21 @@ func (s *DockerRegistrySuite) TestPushBusyboxImage(c *check.C) { dockerCmd(c, "push", repoName) } +func (s *DockerRegistrySuite) TestPushBusyboxImage(c *check.C) { + testPushBusyboxImage(c) +} + +func (s *DockerSchema1RegistrySuite) TestPushBusyboxImage(c *check.C) { + testPushBusyboxImage(c) +} + // pushing an image without a prefix should throw an error func (s *DockerSuite) TestPushUnprefixedRepo(c *check.C) { out, _, err := dockerCmdWithError("push", "busybox") c.Assert(err, check.NotNil, check.Commentf("pushing an unprefixed repo didn't result in a non-zero exit status: %s", out)) } -func (s *DockerRegistrySuite) TestPushUntagged(c *check.C) { +func testPushUntagged(c *check.C) { repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) expected := "Repository does not exist" @@ -39,7 +47,15 @@ func (s *DockerRegistrySuite) TestPushUntagged(c *check.C) { c.Assert(out, checker.Contains, expected, check.Commentf("pushing the image failed")) } -func (s *DockerRegistrySuite) TestPushBadTag(c *check.C) { +func (s *DockerRegistrySuite) TestPushUntagged(c *check.C) { + testPushUntagged(c) +} + +func (s *DockerSchema1RegistrySuite) TestPushUntagged(c *check.C) { + testPushUntagged(c) +} + +func testPushBadTag(c *check.C) { repoName := fmt.Sprintf("%v/dockercli/busybox:latest", privateRegistryURL) expected := "does not exist" @@ -48,7 +64,15 @@ func (s *DockerRegistrySuite) TestPushBadTag(c *check.C) { c.Assert(out, checker.Contains, expected, check.Commentf("pushing the image failed")) } -func (s *DockerRegistrySuite) TestPushMultipleTags(c *check.C) { +func (s *DockerRegistrySuite) TestPushBadTag(c *check.C) { + testPushBadTag(c) +} + +func (s *DockerSchema1RegistrySuite) TestPushBadTag(c *check.C) { + testPushBadTag(c) +} + +func testPushMultipleTags(c *check.C) { repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) repoTag1 := fmt.Sprintf("%v/dockercli/busybox:t1", privateRegistryURL) repoTag2 := fmt.Sprintf("%v/dockercli/busybox:t2", privateRegistryURL) @@ -85,7 +109,15 @@ func (s *DockerRegistrySuite) TestPushMultipleTags(c *check.C) { } } -func (s *DockerRegistrySuite) TestPushEmptyLayer(c *check.C) { +func (s *DockerRegistrySuite) TestPushMultipleTags(c *check.C) { + testPushMultipleTags(c) +} + +func (s *DockerSchema1RegistrySuite) TestPushMultipleTags(c *check.C) { + testPushMultipleTags(c) +} + +func testPushEmptyLayer(c *check.C) { repoName := fmt.Sprintf("%v/dockercli/emptylayer", privateRegistryURL) emptyTarball, err := ioutil.TempFile("", "empty_tarball") c.Assert(err, check.IsNil, check.Commentf("Unable to create test file")) @@ -107,6 +139,14 @@ func (s *DockerRegistrySuite) TestPushEmptyLayer(c *check.C) { c.Assert(err, check.IsNil, check.Commentf("pushing the image to the private registry has failed: %s", out)) } +func (s *DockerRegistrySuite) TestPushEmptyLayer(c *check.C) { + testPushEmptyLayer(c) +} + +func (s *DockerSchema1RegistrySuite) TestPushEmptyLayer(c *check.C) { + testPushEmptyLayer(c) +} + func (s *DockerTrustSuite) TestTrustedPush(c *check.C) { repoName := fmt.Sprintf("%v/dockercli/trusted:latest", privateRegistryURL) // tag the image and upload it to the private registry diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 55bd1295c7..4734c89711 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -1554,9 +1554,9 @@ func daemonTime(c *check.C) time.Time { return dt } -func setupRegistry(c *check.C) *testRegistryV2 { +func setupRegistry(c *check.C, schema1 bool) *testRegistryV2 { testRequires(c, RegistryHosting) - reg, err := newTestRegistryV2(c) + reg, err := newTestRegistryV2(c, schema1) c.Assert(err, check.IsNil) // Wait for registry to be ready to serve requests. diff --git a/integration-cli/registry.go b/integration-cli/registry.go index 35e1b4eb9f..26aebdf7b8 100644 --- a/integration-cli/registry.go +++ b/integration-cli/registry.go @@ -12,14 +12,17 @@ import ( "github.com/go-check/check" ) -const v2binary = "registry-v2" +const ( + v2binary = "registry-v2" + v2binarySchema1 = "registry-v2-schema1" +) type testRegistryV2 struct { cmd *exec.Cmd dir string } -func newTestRegistryV2(c *check.C) (*testRegistryV2, error) { +func newTestRegistryV2(c *check.C, schema1 bool) (*testRegistryV2, error) { template := `version: 0.1 loglevel: debug storage: @@ -41,7 +44,11 @@ http: return nil, err } - cmd := exec.Command(v2binary, confPath) + binary := v2binary + if schema1 { + binary = v2binarySchema1 + } + cmd := exec.Command(binary, confPath) if err := cmd.Start(); err != nil { os.RemoveAll(tmp) if os.IsNotExist(err) {