diff --git a/integration-cli/docker_cli_pull_local_test.go b/integration-cli/docker_cli_pull_local_test.go new file mode 100644 index 0000000000..350e871cf7 --- /dev/null +++ b/integration-cli/docker_cli_pull_local_test.go @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + + "github.com/go-check/check" +) + +// 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) { + repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) + + repos := []string{} + for _, tag := range []string{"recent", "fresh"} { + repos = append(repos, fmt.Sprintf("%v:%v", repoName, tag)) + } + + // Tag and push the same image multiple times. + for _, repo := range repos { + dockerCmd(c, "tag", "busybox", repo) + dockerCmd(c, "push", repo) + } + + // Clear local images store. + args := append([]string{"rmi"}, repos...) + dockerCmd(c, args...) + + // Pull a single tag and verify it doesn't bring down all aliases. + dockerCmd(c, "pull", repos[0]) + dockerCmd(c, "inspect", repos[0]) + for _, repo := range repos[1:] { + if _, _, err := dockerCmdWithError("inspect", repo); err == nil { + c.Fatalf("Image %v shouldn't have been pulled down", repo) + } + } +} diff --git a/integration-cli/docker_cli_pull_test.go b/integration-cli/docker_cli_pull_test.go index d97934a878..d511563462 100644 --- a/integration-cli/docker_cli_pull_test.go +++ b/integration-cli/docker_cli_pull_test.go @@ -2,407 +2,154 @@ package main import ( "fmt" - "os/exec" + "regexp" "strings" "time" - "io/ioutil" - + "github.com/docker/distribution/digest" + "github.com/docker/docker/integration-cli/checker" "github.com/go-check/check" ) -// See issue docker/docker#8141 -func (s *DockerRegistrySuite) TestPullImageWithAliases(c *check.C) { - repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) +// TestPullFromCentralRegistry pulls an image from the central registry and verifies that the client +// prints all expected output. +func (s *DockerHubPullSuite) TestPullFromCentralRegistry(c *check.C) { + out := s.Cmd(c, "pull", "hello-world") + defer deleteImages("hello-world") - repos := []string{} - for _, tag := range []string{"recent", "fresh"} { - repos = append(repos, fmt.Sprintf("%v:%v", repoName, tag)) - } + c.Assert(out, checker.Contains, "Using default tag: latest", check.Commentf("expected the 'latest' tag to be automatically assumed")) + c.Assert(out, checker.Contains, "Pulling from library/hello-world", check.Commentf("expected the 'library/' prefix to be automatically assumed")) + c.Assert(out, checker.Contains, "Downloaded newer image for hello-world:latest") - // Tag and push the same image multiple times. - for _, repo := range repos { - dockerCmd(c, "tag", "busybox", repo) - dockerCmd(c, "push", repo) - } + matches := regexp.MustCompile(`Digest: (.+)\n`).FindAllStringSubmatch(out, -1) + c.Assert(len(matches), checker.Equals, 1, check.Commentf("expected exactly one image digest in the output")) + c.Assert(len(matches[0]), checker.Equals, 2, check.Commentf("unexpected number of submatches for the digest")) + _, err := digest.ParseDigest(matches[0][1]) + c.Check(err, checker.IsNil, check.Commentf("invalid digest %q in output", matches[0][1])) - // Clear local images store. - args := append([]string{"rmi"}, repos...) - dockerCmd(c, args...) - - // Pull a single tag and verify it doesn't bring down all aliases. - dockerCmd(c, "pull", repos[0]) - dockerCmd(c, "inspect", repos[0]) - for _, repo := range repos[1:] { - if _, _, err := dockerCmdWithError("inspect", repo); err == nil { - c.Fatalf("Image %v shouldn't have been pulled down", repo) - } + // We should have a single entry in images. + img := strings.TrimSpace(s.Cmd(c, "images")) + if splitImg := strings.Split(img, "\n"); len(splitImg) != 2 { + c.Fatalf("expected only two lines in the output of `docker images`, got %d", len(splitImg)) + } else if re := regexp.MustCompile(`^hello-world\s+latest`); !re.Match([]byte(splitImg[1])) { + c.Fatal("invalid output for `docker images` (expected image and tag name") } } -// pulling library/hello-world should show verified message -func (s *DockerSuite) TestPullVerified(c *check.C) { - c.Skip("Skipping hub dependent test") - - // Image must be pulled from central repository to get verified message - // unless keychain is manually updated to contain the daemon's sign key. - - verifiedName := "hello-world" - - // pull it - expected := "The image you are pulling has been verified" - if out, exitCode, err := dockerCmdWithError("pull", verifiedName); err != nil || !strings.Contains(out, expected) { - if err != nil || exitCode != 0 { - c.Skip(fmt.Sprintf("pulling the '%s' image from the registry has failed: %v", verifiedName, err)) - } - c.Fatalf("pulling a verified image failed. expected: %s\ngot: %s, %v", expected, out, err) - } - - // pull it again - if out, exitCode, err := dockerCmdWithError("pull", verifiedName); err != nil || strings.Contains(out, expected) { - if err != nil || exitCode != 0 { - c.Skip(fmt.Sprintf("pulling the '%s' image from the registry has failed: %v", verifiedName, err)) - } - c.Fatalf("pulling a verified image failed. unexpected verify message\ngot: %s, %v", out, err) - } - -} - -// pulling an image from the central registry should work -func (s *DockerSuite) TestPullImageFromCentralRegistry(c *check.C) { - testRequires(c, Network) - - dockerCmd(c, "pull", "hello-world") -} - -// pulling a non-existing image from the central registry should return a non-zero exit code -func (s *DockerSuite) TestPullNonExistingImage(c *check.C) { - testRequires(c, Network) - - name := "sadfsadfasdf" - out, _, err := dockerCmdWithError("pull", name) - - if err == nil || !strings.Contains(out, fmt.Sprintf("Error: image library/%s:latest not found", name)) { - c.Fatalf("expected non-zero exit status when pulling non-existing image: %s", out) +// TestPullNonExistingImage pulls non-existing images from the central registry, with different +// combinations of implicit tag and library prefix. +func (s *DockerHubPullSuite) TestPullNonExistingImage(c *check.C) { + for _, e := range []struct { + Image string + Alias string + }{ + {"library/asdfasdf:foobar", "asdfasdf:foobar"}, + {"library/asdfasdf:foobar", "library/asdfasdf:foobar"}, + {"library/asdfasdf:latest", "asdfasdf"}, + {"library/asdfasdf:latest", "asdfasdf:latest"}, + {"library/asdfasdf:latest", "library/asdfasdf"}, + {"library/asdfasdf:latest", "library/asdfasdf:latest"}, + } { + out, err := s.CmdWithError("pull", e.Alias) + c.Assert(err, checker.NotNil, check.Commentf("expected non-zero exit status when pulling non-existing image: %s", out)) + c.Assert(out, checker.Contains, fmt.Sprintf("Error: image %s not found", e.Image), check.Commentf("expected image not found error messages")) } } -// pulling an image from the central registry using official names should work -// ensure all pulls result in the same image -func (s *DockerSuite) TestPullImageOfficialNames(c *check.C) { - testRequires(c, Network) +// TestPullFromCentralRegistryImplicitRefParts pulls an image from the central registry and verifies +// that pulling the same image with different combinations of implicit elements of the the image +// reference (tag, repository, central registry url, ...) doesn't trigger a new pull nor leads to +// multiple images. +func (s *DockerHubPullSuite) TestPullFromCentralRegistryImplicitRefParts(c *check.C) { + s.Cmd(c, "pull", "hello-world") + defer deleteImages("hello-world") - names := []string{ + for _, i := range []string{ + "hello-world", + "hello-world:latest", "library/hello-world", + "library/hello-world:latest", "docker.io/library/hello-world", "index.docker.io/library/hello-world", + } { + out := s.Cmd(c, "pull", i) + c.Assert(out, checker.Contains, "Image is up to date for hello-world:latest") } - for _, name := range names { - out, exitCode, err := dockerCmdWithError("pull", name) - if err != nil || exitCode != 0 { - c.Errorf("pulling the '%s' image from the registry has failed: %s", name, err) - continue - } - // ensure we don't have multiple image names. - out, _ = dockerCmd(c, "images") - if strings.Contains(out, name) { - c.Errorf("images should not have listed '%s'", name) + // We should have a single entry in images. + img := strings.TrimSpace(s.Cmd(c, "images")) + if splitImg := strings.Split(img, "\n"); len(splitImg) != 2 { + c.Fatalf("expected only two lines in the output of `docker images`, got %d", len(splitImg)) + } else if re := regexp.MustCompile(`^hello-world\s+latest`); !re.Match([]byte(splitImg[1])) { + c.Fatal("invalid output for `docker images` (expected image and tag name") + } +} + +// TestPullScratchNotAllowed verifies that pulling 'scratch' is rejected. +func (s *DockerHubPullSuite) TestPullScratchNotAllowed(c *check.C) { + out, err := s.CmdWithError("pull", "scratch") + c.Assert(err, checker.NotNil, check.Commentf("expected pull of scratch to fail")) + c.Assert(out, checker.Contains, "'scratch' is a reserved name") + c.Assert(out, checker.Not(checker.Contains), "Pulling repository scratch") +} + +// TestPullAllTagsFromCentralRegistry pulls using `all-tags` for a given image and verifies that it +// results in more images than a naked pull. +func (s *DockerHubPullSuite) TestPullAllTagsFromCentralRegistry(c *check.C) { + s.Cmd(c, "pull", "busybox") + outImageCmd := s.Cmd(c, "images", "busybox") + splitOutImageCmd := strings.Split(strings.TrimSpace(outImageCmd), "\n") + c.Assert(splitOutImageCmd, checker.HasLen, 2, check.Commentf("expected a single entry in images\n%v", outImageCmd)) + + s.Cmd(c, "pull", "--all-tags=true", "busybox") + outImageAllTagCmd := s.Cmd(c, "images", "busybox") + if linesCount := strings.Count(outImageAllTagCmd, "\n"); linesCount <= 2 { + c.Fatalf("pulling all tags should provide more images, got %d", linesCount-1) + } + + // Verify that the line for 'busybox:latest' is left unchanged. + var latestLine string + for _, line := range strings.Split(outImageAllTagCmd, "\n") { + if strings.HasPrefix(line, "busybox") && strings.Contains(line, "latest") { + latestLine = line + break } } + c.Assert(latestLine, checker.Not(checker.Equals), "", check.Commentf("no entry for busybox:latest found after pulling all tags")) + splitLatest := strings.Fields(latestLine) + splitCurrent := strings.Fields(splitOutImageCmd[1]) + c.Assert(splitLatest, checker.DeepEquals, splitCurrent, check.Commentf("busybox:latest was changed after pulling all tags")) } -func (s *DockerSuite) TestPullScratchNotAllowed(c *check.C) { - testRequires(c, Network) - - out, exitCode, err := dockerCmdWithError("pull", "scratch") - if err == nil { - c.Fatal("expected pull of scratch to fail, but it didn't") - } - if exitCode != 1 { - c.Fatalf("pulling scratch expected exit code 1, got %d", exitCode) - } - if strings.Contains(out, "Pulling repository scratch") { - c.Fatalf("pulling scratch should not have begun: %s", out) - } - if !strings.Contains(out, "'scratch' is a reserved name") { - c.Fatalf("unexpected output pulling scratch: %s", out) - } -} - -// pulling an image with --all-tags=true -func (s *DockerSuite) TestPullImageWithAllTagFromCentralRegistry(c *check.C) { - testRequires(c, Network) - - dockerCmd(c, "pull", "busybox") - - outImageCmd, _ := dockerCmd(c, "images", "busybox") - - dockerCmd(c, "pull", "--all-tags=true", "busybox") - - outImageAllTagCmd, _ := dockerCmd(c, "images", "busybox") - - if strings.Count(outImageCmd, "busybox") >= strings.Count(outImageAllTagCmd, "busybox") { - c.Fatalf("Pulling with all tags should get more images") - } - - // FIXME has probably no effect (tags already pushed) - dockerCmd(c, "pull", "-a", "busybox") - - outImageAllTagCmd, _ = dockerCmd(c, "images", "busybox") - - if strings.Count(outImageCmd, "busybox") >= strings.Count(outImageAllTagCmd, "busybox") { - c.Fatalf("Pulling with all tags should get more images") - } -} - -func (s *DockerTrustSuite) TestTrustedPull(c *check.C) { - repoName := s.setupTrustedImage(c, "trusted-pull") - - // Try pull - pullCmd := exec.Command(dockerBinary, "pull", repoName) - s.trustedCmd(pullCmd) - out, _, err := runCommandWithOutput(pullCmd) - if err != nil { - c.Fatalf("Error running trusted pull: %s\n%s", err, out) - } - - if !strings.Contains(string(out), "Tagging") { - c.Fatalf("Missing expected output on trusted push:\n%s", out) - } - - dockerCmd(c, "rmi", repoName) - - // Try untrusted pull to ensure we pushed the tag to the registry - pullCmd = exec.Command(dockerBinary, "pull", "--disable-content-trust=true", repoName) - s.trustedCmd(pullCmd) - out, _, err = runCommandWithOutput(pullCmd) - if err != nil { - c.Fatalf("Error running trusted pull: %s\n%s", err, out) - } - - if !strings.Contains(string(out), "Status: Downloaded") { - c.Fatalf("Missing expected output on trusted pull with --disable-content-trust:\n%s", out) - } -} - -func (s *DockerTrustSuite) TestTrustedIsolatedPull(c *check.C) { - repoName := s.setupTrustedImage(c, "trusted-isolatd-pull") - - // Try pull (run from isolated directory without trust information) - pullCmd := exec.Command(dockerBinary, "--config", "/tmp/docker-isolated", "pull", repoName) - s.trustedCmd(pullCmd) - out, _, err := runCommandWithOutput(pullCmd) - if err != nil { - c.Fatalf("Error running trusted pull: %s\n%s", err, out) - } - - if !strings.Contains(string(out), "Tagging") { - c.Fatalf("Missing expected output on trusted push:\n%s", out) - } - - dockerCmd(c, "rmi", repoName) -} - -func (s *DockerTrustSuite) TestUntrustedPull(c *check.C) { - repoName := fmt.Sprintf("%v/dockercli/trusted:latest", privateRegistryURL) - // tag the image and upload it to the private registry - dockerCmd(c, "tag", "busybox", repoName) - dockerCmd(c, "push", repoName) - dockerCmd(c, "rmi", repoName) - - // Try trusted pull on untrusted tag - pullCmd := exec.Command(dockerBinary, "pull", repoName) - s.trustedCmd(pullCmd) - out, _, err := runCommandWithOutput(pullCmd) - if err == nil { - c.Fatalf("Error expected when running trusted pull with:\n%s", out) - } - - if !strings.Contains(string(out), "no trust data available") { - c.Fatalf("Missing expected output on trusted pull:\n%s", out) - } -} - -func (s *DockerTrustSuite) TestPullWhenCertExpired(c *check.C) { - c.Skip("Currently changes system time, causing instability") - repoName := s.setupTrustedImage(c, "trusted-cert-expired") - - // Certificates have 10 years of expiration - elevenYearsFromNow := time.Now().Add(time.Hour * 24 * 365 * 11) - - runAtDifferentDate(elevenYearsFromNow, func() { - // Try pull - pullCmd := exec.Command(dockerBinary, "pull", repoName) - s.trustedCmd(pullCmd) - out, _, err := runCommandWithOutput(pullCmd) - if err == nil { - c.Fatalf("Error running trusted pull in the distant future: %s\n%s", err, out) - } - - if !strings.Contains(string(out), "could not validate the path to a trusted root") { - c.Fatalf("Missing expected output on trusted pull in the distant future:\n%s", out) - } - }) - - runAtDifferentDate(elevenYearsFromNow, func() { - // Try pull - pullCmd := exec.Command(dockerBinary, "pull", "--disable-content-trust", repoName) - s.trustedCmd(pullCmd) - out, _, err := runCommandWithOutput(pullCmd) - if err != nil { - c.Fatalf("Error running untrusted pull in the distant future: %s\n%s", err, out) - } - - if !strings.Contains(string(out), "Status: Downloaded") { - c.Fatalf("Missing expected output on untrusted pull in the distant future:\n%s", out) - } - }) -} - -func (s *DockerTrustSuite) TestTrustedPullFromBadTrustServer(c *check.C) { - repoName := fmt.Sprintf("%v/dockerclievilpull/trusted:latest", privateRegistryURL) - evilLocalConfigDir, err := ioutil.TempDir("", "evil-local-config-dir") - if err != nil { - c.Fatalf("Failed to create local temp dir") - } - - // tag the image and upload it to the private registry - dockerCmd(c, "tag", "busybox", repoName) - - pushCmd := exec.Command(dockerBinary, "push", repoName) - s.trustedCmd(pushCmd) - out, _, err := runCommandWithOutput(pushCmd) - if err != nil { - c.Fatalf("Error running trusted push: %s\n%s", err, out) - } - if !strings.Contains(string(out), "Signing and pushing trust metadata") { - c.Fatalf("Missing expected output on trusted push:\n%s", out) - } - - dockerCmd(c, "rmi", repoName) - - // Try pull - pullCmd := exec.Command(dockerBinary, "pull", repoName) - s.trustedCmd(pullCmd) - out, _, err = runCommandWithOutput(pullCmd) - if err != nil { - c.Fatalf("Error running trusted pull: %s\n%s", err, out) - } - - if !strings.Contains(string(out), "Tagging") { - c.Fatalf("Missing expected output on trusted push:\n%s", out) - } - - dockerCmd(c, "rmi", repoName) - - // Kill the notary server, start a new "evil" one. - s.not.Close() - s.not, err = newTestNotary(c) - if err != nil { - c.Fatalf("Restarting notary server failed.") - } - - // In order to make an evil server, lets re-init a client (with a different trust dir) and push new data. - // tag an image and upload it to the private registry - dockerCmd(c, "--config", evilLocalConfigDir, "tag", "busybox", repoName) - - // Push up to the new server - pushCmd = exec.Command(dockerBinary, "--config", evilLocalConfigDir, "push", repoName) - s.trustedCmd(pushCmd) - out, _, err = runCommandWithOutput(pushCmd) - if err != nil { - c.Fatalf("Error running trusted push: %s\n%s", err, out) - } - if !strings.Contains(string(out), "Signing and pushing trust metadata") { - c.Fatalf("Missing expected output on trusted push:\n%s", out) - } - - // Now, try pulling with the original client from this new trust server. This should fail. - pullCmd = exec.Command(dockerBinary, "pull", repoName) - s.trustedCmd(pullCmd) - out, _, err = runCommandWithOutput(pullCmd) - if err == nil { - c.Fatalf("Expected to fail on this pull due to different remote data: %s\n%s", err, out) - } - - if !strings.Contains(string(out), "failed to validate data with current trusted certificates") { - c.Fatalf("Missing expected output on trusted push:\n%s", out) - } -} - -func (s *DockerTrustSuite) TestTrustedPullWithExpiredSnapshot(c *check.C) { - c.Skip("Currently changes system time, causing instability") - repoName := fmt.Sprintf("%v/dockercliexpiredtimestamppull/trusted:latest", privateRegistryURL) - // tag the image and upload it to the private registry - dockerCmd(c, "tag", "busybox", repoName) - - // Push with default passphrases - pushCmd := exec.Command(dockerBinary, "push", repoName) - s.trustedCmd(pushCmd) - out, _, err := runCommandWithOutput(pushCmd) - if err != nil { - c.Fatalf("trusted push failed: %s\n%s", err, out) - } - - if !strings.Contains(string(out), "Signing and pushing trust metadata") { - c.Fatalf("Missing expected output on trusted push:\n%s", out) - } - - dockerCmd(c, "rmi", repoName) - - // Snapshots last for three years. This should be expired - fourYearsLater := time.Now().Add(time.Hour * 24 * 365 * 4) - - // Should succeed because the server transparently re-signs one - runAtDifferentDate(fourYearsLater, func() { - // Try pull - pullCmd := exec.Command(dockerBinary, "pull", repoName) - s.trustedCmd(pullCmd) - out, _, err = runCommandWithOutput(pullCmd) - if err == nil { - c.Fatalf("Missing expected error running trusted pull with expired snapshots") - } - - if !strings.Contains(string(out), "repository out-of-date") { - c.Fatalf("Missing expected output on trusted pull with expired snapshot:\n%s", out) - } - }) -} - -// Test that pull continues after client has disconnected. #15589 -func (s *DockerSuite) TestPullClientDisconnect(c *check.C) { - testRequires(c, Network) - +// TestPullClientDisconnect kills the client during a pull operation and verifies that the operation +// still succesfully completes on the daemon side. +// +// Ref: docker/docker#15589 +func (s *DockerHubPullSuite) TestPullClientDisconnect(c *check.C) { repoName := "hello-world:latest" - dockerCmdWithError("rmi", repoName) // clean just in case - - pullCmd := exec.Command(dockerBinary, "pull", repoName) - + pullCmd := s.MakeCmd("pull", repoName) stdout, err := pullCmd.StdoutPipe() - c.Assert(err, check.IsNil) - + c.Assert(err, checker.IsNil) err = pullCmd.Start() - c.Assert(err, check.IsNil) + c.Assert(err, checker.IsNil) - // cancel as soon as we get some output + // Cancel as soon as we get some output. buf := make([]byte, 10) _, err = stdout.Read(buf) - c.Assert(err, check.IsNil) + c.Assert(err, checker.IsNil) err = pullCmd.Process.Kill() - c.Assert(err, check.IsNil) + c.Assert(err, checker.IsNil) maxAttempts := 20 for i := 0; ; i++ { - if _, _, err := dockerCmdWithError("inspect", repoName); err == nil { + if _, err := s.CmdWithError("inspect", repoName); err == nil { break } if i >= maxAttempts { - c.Fatal("Timeout reached. Image was not pulled after client disconnected.") + c.Fatal("timeout reached: image was not pulled after client disconnected") } time.Sleep(500 * time.Millisecond) } - } diff --git a/integration-cli/docker_cli_pull_trusted_test.go b/integration-cli/docker_cli_pull_trusted_test.go new file mode 100644 index 0000000000..d0514573c7 --- /dev/null +++ b/integration-cli/docker_cli_pull_trusted_test.go @@ -0,0 +1,225 @@ +package main + +import ( + "fmt" + "io/ioutil" + "os/exec" + "strings" + "time" + + "github.com/go-check/check" +) + +func (s *DockerTrustSuite) TestTrustedPull(c *check.C) { + repoName := s.setupTrustedImage(c, "trusted-pull") + + // Try pull + pullCmd := exec.Command(dockerBinary, "pull", repoName) + s.trustedCmd(pullCmd) + out, _, err := runCommandWithOutput(pullCmd) + if err != nil { + c.Fatalf("Error running trusted pull: %s\n%s", err, out) + } + + if !strings.Contains(string(out), "Tagging") { + c.Fatalf("Missing expected output on trusted push:\n%s", out) + } + + dockerCmd(c, "rmi", repoName) + + // Try untrusted pull to ensure we pushed the tag to the registry + pullCmd = exec.Command(dockerBinary, "pull", "--disable-content-trust=true", repoName) + s.trustedCmd(pullCmd) + out, _, err = runCommandWithOutput(pullCmd) + if err != nil { + c.Fatalf("Error running trusted pull: %s\n%s", err, out) + } + + if !strings.Contains(string(out), "Status: Downloaded") { + c.Fatalf("Missing expected output on trusted pull with --disable-content-trust:\n%s", out) + } +} + +func (s *DockerTrustSuite) TestTrustedIsolatedPull(c *check.C) { + repoName := s.setupTrustedImage(c, "trusted-isolatd-pull") + + // Try pull (run from isolated directory without trust information) + pullCmd := exec.Command(dockerBinary, "--config", "/tmp/docker-isolated", "pull", repoName) + s.trustedCmd(pullCmd) + out, _, err := runCommandWithOutput(pullCmd) + if err != nil { + c.Fatalf("Error running trusted pull: %s\n%s", err, out) + } + + if !strings.Contains(string(out), "Tagging") { + c.Fatalf("Missing expected output on trusted push:\n%s", out) + } + + dockerCmd(c, "rmi", repoName) +} + +func (s *DockerTrustSuite) TestUntrustedPull(c *check.C) { + repoName := fmt.Sprintf("%v/dockercli/trusted:latest", privateRegistryURL) + // tag the image and upload it to the private registry + dockerCmd(c, "tag", "busybox", repoName) + dockerCmd(c, "push", repoName) + dockerCmd(c, "rmi", repoName) + + // Try trusted pull on untrusted tag + pullCmd := exec.Command(dockerBinary, "pull", repoName) + s.trustedCmd(pullCmd) + out, _, err := runCommandWithOutput(pullCmd) + if err == nil { + c.Fatalf("Error expected when running trusted pull with:\n%s", out) + } + + if !strings.Contains(string(out), "no trust data available") { + c.Fatalf("Missing expected output on trusted pull:\n%s", out) + } +} + +func (s *DockerTrustSuite) TestPullWhenCertExpired(c *check.C) { + c.Skip("Currently changes system time, causing instability") + repoName := s.setupTrustedImage(c, "trusted-cert-expired") + + // Certificates have 10 years of expiration + elevenYearsFromNow := time.Now().Add(time.Hour * 24 * 365 * 11) + + runAtDifferentDate(elevenYearsFromNow, func() { + // Try pull + pullCmd := exec.Command(dockerBinary, "pull", repoName) + s.trustedCmd(pullCmd) + out, _, err := runCommandWithOutput(pullCmd) + if err == nil { + c.Fatalf("Error running trusted pull in the distant future: %s\n%s", err, out) + } + + if !strings.Contains(string(out), "could not validate the path to a trusted root") { + c.Fatalf("Missing expected output on trusted pull in the distant future:\n%s", out) + } + }) + + runAtDifferentDate(elevenYearsFromNow, func() { + // Try pull + pullCmd := exec.Command(dockerBinary, "pull", "--disable-content-trust", repoName) + s.trustedCmd(pullCmd) + out, _, err := runCommandWithOutput(pullCmd) + if err != nil { + c.Fatalf("Error running untrusted pull in the distant future: %s\n%s", err, out) + } + + if !strings.Contains(string(out), "Status: Downloaded") { + c.Fatalf("Missing expected output on untrusted pull in the distant future:\n%s", out) + } + }) +} + +func (s *DockerTrustSuite) TestTrustedPullFromBadTrustServer(c *check.C) { + repoName := fmt.Sprintf("%v/dockerclievilpull/trusted:latest", privateRegistryURL) + evilLocalConfigDir, err := ioutil.TempDir("", "evil-local-config-dir") + if err != nil { + c.Fatalf("Failed to create local temp dir") + } + + // tag the image and upload it to the private registry + dockerCmd(c, "tag", "busybox", repoName) + + pushCmd := exec.Command(dockerBinary, "push", repoName) + s.trustedCmd(pushCmd) + out, _, err := runCommandWithOutput(pushCmd) + if err != nil { + c.Fatalf("Error running trusted push: %s\n%s", err, out) + } + if !strings.Contains(string(out), "Signing and pushing trust metadata") { + c.Fatalf("Missing expected output on trusted push:\n%s", out) + } + + dockerCmd(c, "rmi", repoName) + + // Try pull + pullCmd := exec.Command(dockerBinary, "pull", repoName) + s.trustedCmd(pullCmd) + out, _, err = runCommandWithOutput(pullCmd) + if err != nil { + c.Fatalf("Error running trusted pull: %s\n%s", err, out) + } + + if !strings.Contains(string(out), "Tagging") { + c.Fatalf("Missing expected output on trusted push:\n%s", out) + } + + dockerCmd(c, "rmi", repoName) + + // Kill the notary server, start a new "evil" one. + s.not.Close() + s.not, err = newTestNotary(c) + if err != nil { + c.Fatalf("Restarting notary server failed.") + } + + // In order to make an evil server, lets re-init a client (with a different trust dir) and push new data. + // tag an image and upload it to the private registry + dockerCmd(c, "--config", evilLocalConfigDir, "tag", "busybox", repoName) + + // Push up to the new server + pushCmd = exec.Command(dockerBinary, "--config", evilLocalConfigDir, "push", repoName) + s.trustedCmd(pushCmd) + out, _, err = runCommandWithOutput(pushCmd) + if err != nil { + c.Fatalf("Error running trusted push: %s\n%s", err, out) + } + if !strings.Contains(string(out), "Signing and pushing trust metadata") { + c.Fatalf("Missing expected output on trusted push:\n%s", out) + } + + // Now, try pulling with the original client from this new trust server. This should fail. + pullCmd = exec.Command(dockerBinary, "pull", repoName) + s.trustedCmd(pullCmd) + out, _, err = runCommandWithOutput(pullCmd) + if err == nil { + c.Fatalf("Expected to fail on this pull due to different remote data: %s\n%s", err, out) + } + + if !strings.Contains(string(out), "failed to validate data with current trusted certificates") { + c.Fatalf("Missing expected output on trusted push:\n%s", out) + } +} + +func (s *DockerTrustSuite) TestTrustedPullWithExpiredSnapshot(c *check.C) { + c.Skip("Currently changes system time, causing instability") + repoName := fmt.Sprintf("%v/dockercliexpiredtimestamppull/trusted:latest", privateRegistryURL) + // tag the image and upload it to the private registry + dockerCmd(c, "tag", "busybox", repoName) + + // Push with default passphrases + pushCmd := exec.Command(dockerBinary, "push", repoName) + s.trustedCmd(pushCmd) + out, _, err := runCommandWithOutput(pushCmd) + if err != nil { + c.Fatalf("trusted push failed: %s\n%s", err, out) + } + + if !strings.Contains(string(out), "Signing and pushing trust metadata") { + c.Fatalf("Missing expected output on trusted push:\n%s", out) + } + + dockerCmd(c, "rmi", repoName) + + // Snapshots last for three years. This should be expired + fourYearsLater := time.Now().Add(time.Hour * 24 * 365 * 4) + + // Should succeed because the server transparently re-signs one + runAtDifferentDate(fourYearsLater, func() { + // Try pull + pullCmd := exec.Command(dockerBinary, "pull", repoName) + s.trustedCmd(pullCmd) + out, _, err = runCommandWithOutput(pullCmd) + if err == nil { + c.Fatalf("Missing expected error running trusted pull with expired snapshots") + } + + if !strings.Contains(string(out), "repository out-of-date") { + c.Fatalf("Missing expected output on trusted pull with expired snapshot:\n%s", out) + } + }) +}