Selaa lähdekoodia

distribution: errors: do not access the errors slice if it's empty

- cherry-pick from 1.10.3 branch: 0186f4d4223a094a050d06f456355da3ae431468
- add token service test suite
- add integration test (missing in 1.10.3 branch)

Signed-off-by: Antonio Murdaca <runcom@redhat.com>
Antonio Murdaca 9 vuotta sitten
vanhempi
commit
1b5c2e1d72

+ 3 - 1
distribution/errors.go

@@ -84,7 +84,9 @@ func continueOnError(err error) bool {
 func retryOnError(err error) error {
 func retryOnError(err error) error {
 	switch v := err.(type) {
 	switch v := err.(type) {
 	case errcode.Errors:
 	case errcode.Errors:
-		return retryOnError(v[0])
+		if len(v) != 0 {
+			return retryOnError(v[0])
+		}
 	case errcode.Error:
 	case errcode.Error:
 		switch v.Code {
 		switch v.Code {
 		case errcode.ErrorCodeUnauthorized, errcode.ErrorCodeUnsupported, errcode.ErrorCodeDenied:
 		case errcode.ErrorCodeUnauthorized, errcode.ErrorCodeUnsupported, errcode.ErrorCodeDenied:

+ 44 - 8
integration-cli/check_test.go

@@ -49,7 +49,7 @@ type DockerRegistrySuite struct {
 
 
 func (s *DockerRegistrySuite) SetUpTest(c *check.C) {
 func (s *DockerRegistrySuite) SetUpTest(c *check.C) {
 	testRequires(c, DaemonIsLinux, RegistryHosting)
 	testRequires(c, DaemonIsLinux, RegistryHosting)
-	s.reg = setupRegistry(c, false, false)
+	s.reg = setupRegistry(c, false, "", "")
 	s.d = NewDaemon(c)
 	s.d = NewDaemon(c)
 }
 }
 
 
@@ -77,7 +77,7 @@ type DockerSchema1RegistrySuite struct {
 
 
 func (s *DockerSchema1RegistrySuite) SetUpTest(c *check.C) {
 func (s *DockerSchema1RegistrySuite) SetUpTest(c *check.C) {
 	testRequires(c, DaemonIsLinux, RegistryHosting)
 	testRequires(c, DaemonIsLinux, RegistryHosting)
-	s.reg = setupRegistry(c, true, false)
+	s.reg = setupRegistry(c, true, "", "")
 	s.d = NewDaemon(c)
 	s.d = NewDaemon(c)
 }
 }
 
 
@@ -92,24 +92,24 @@ func (s *DockerSchema1RegistrySuite) TearDownTest(c *check.C) {
 }
 }
 
 
 func init() {
 func init() {
-	check.Suite(&DockerRegistryAuthSuite{
+	check.Suite(&DockerRegistryAuthHtpasswdSuite{
 		ds: &DockerSuite{},
 		ds: &DockerSuite{},
 	})
 	})
 }
 }
 
 
-type DockerRegistryAuthSuite struct {
+type DockerRegistryAuthHtpasswdSuite struct {
 	ds  *DockerSuite
 	ds  *DockerSuite
 	reg *testRegistryV2
 	reg *testRegistryV2
 	d   *Daemon
 	d   *Daemon
 }
 }
 
 
-func (s *DockerRegistryAuthSuite) SetUpTest(c *check.C) {
+func (s *DockerRegistryAuthHtpasswdSuite) SetUpTest(c *check.C) {
 	testRequires(c, DaemonIsLinux, RegistryHosting)
 	testRequires(c, DaemonIsLinux, RegistryHosting)
-	s.reg = setupRegistry(c, false, true)
+	s.reg = setupRegistry(c, false, "htpasswd", "")
 	s.d = NewDaemon(c)
 	s.d = NewDaemon(c)
 }
 }
 
 
-func (s *DockerRegistryAuthSuite) TearDownTest(c *check.C) {
+func (s *DockerRegistryAuthHtpasswdSuite) TearDownTest(c *check.C) {
 	if s.reg != nil {
 	if s.reg != nil {
 		out, err := s.d.Cmd("logout", privateRegistryURL)
 		out, err := s.d.Cmd("logout", privateRegistryURL)
 		c.Assert(err, check.IsNil, check.Commentf(out))
 		c.Assert(err, check.IsNil, check.Commentf(out))
@@ -121,6 +121,42 @@ func (s *DockerRegistryAuthSuite) TearDownTest(c *check.C) {
 	s.ds.TearDownTest(c)
 	s.ds.TearDownTest(c)
 }
 }
 
 
+func init() {
+	check.Suite(&DockerRegistryAuthTokenSuite{
+		ds: &DockerSuite{},
+	})
+}
+
+type DockerRegistryAuthTokenSuite struct {
+	ds  *DockerSuite
+	reg *testRegistryV2
+	d   *Daemon
+}
+
+func (s *DockerRegistryAuthTokenSuite) SetUpTest(c *check.C) {
+	testRequires(c, DaemonIsLinux, RegistryHosting)
+	s.d = NewDaemon(c)
+}
+
+func (s *DockerRegistryAuthTokenSuite) TearDownTest(c *check.C) {
+	if s.reg != nil {
+		out, err := s.d.Cmd("logout", privateRegistryURL)
+		c.Assert(err, check.IsNil, check.Commentf(out))
+		s.reg.Close()
+	}
+	if s.d != nil {
+		s.d.Stop()
+	}
+	s.ds.TearDownTest(c)
+}
+
+func (s *DockerRegistryAuthTokenSuite) setupRegistryWithTokenService(c *check.C, tokenURL string) {
+	if s == nil {
+		c.Fatal("registry suite isn't initialized")
+	}
+	s.reg = setupRegistry(c, false, "token", tokenURL)
+}
+
 func init() {
 func init() {
 	check.Suite(&DockerDaemonSuite{
 	check.Suite(&DockerDaemonSuite{
 		ds: &DockerSuite{},
 		ds: &DockerSuite{},
@@ -159,7 +195,7 @@ type DockerTrustSuite struct {
 
 
 func (s *DockerTrustSuite) SetUpTest(c *check.C) {
 func (s *DockerTrustSuite) SetUpTest(c *check.C) {
 	testRequires(c, RegistryHosting, NotaryServerHosting)
 	testRequires(c, RegistryHosting, NotaryServerHosting)
-	s.reg = setupRegistry(c, false, false)
+	s.reg = setupRegistry(c, false, "", "")
 	s.not = setupNotary(c)
 	s.not = setupNotary(c)
 }
 }
 
 

+ 2 - 2
integration-cli/docker_cli_build_test.go

@@ -6596,7 +6596,7 @@ func (s *DockerSuite) TestBuildWorkdirWindowsPath(c *check.C) {
 	}
 	}
 }
 }
 
 
-func (s *DockerRegistryAuthSuite) TestBuildFromAuthenticatedRegistry(c *check.C) {
+func (s *DockerRegistryAuthHtpasswdSuite) TestBuildFromAuthenticatedRegistry(c *check.C) {
 	dockerCmd(c, "login", "-u", s.reg.username, "-p", s.reg.password, privateRegistryURL)
 	dockerCmd(c, "login", "-u", s.reg.username, "-p", s.reg.password, privateRegistryURL)
 
 
 	baseImage := privateRegistryURL + "/baseimage"
 	baseImage := privateRegistryURL + "/baseimage"
@@ -6619,7 +6619,7 @@ func (s *DockerRegistryAuthSuite) TestBuildFromAuthenticatedRegistry(c *check.C)
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
 }
 }
 
 
-func (s *DockerRegistryAuthSuite) TestBuildWithExternalAuth(c *check.C) {
+func (s *DockerRegistryAuthHtpasswdSuite) TestBuildWithExternalAuth(c *check.C) {
 	osPath := os.Getenv("PATH")
 	osPath := os.Getenv("PATH")
 	defer os.Setenv("PATH", osPath)
 	defer os.Setenv("PATH", osPath)
 
 

+ 2 - 2
integration-cli/docker_cli_login_test.go

@@ -19,7 +19,7 @@ func (s *DockerSuite) TestLoginWithoutTTY(c *check.C) {
 	c.Assert(err, checker.NotNil) //"Expected non nil err when loginning in & TTY not available"
 	c.Assert(err, checker.NotNil) //"Expected non nil err when loginning in & TTY not available"
 }
 }
 
 
-func (s *DockerRegistryAuthSuite) TestLoginToPrivateRegistry(c *check.C) {
+func (s *DockerRegistryAuthHtpasswdSuite) TestLoginToPrivateRegistry(c *check.C) {
 	// wrong credentials
 	// wrong credentials
 	out, _, err := dockerCmdWithError("login", "-u", s.reg.username, "-p", "WRONGPASSWORD", privateRegistryURL)
 	out, _, err := dockerCmdWithError("login", "-u", s.reg.username, "-p", "WRONGPASSWORD", privateRegistryURL)
 	c.Assert(err, checker.NotNil, check.Commentf(out))
 	c.Assert(err, checker.NotNil, check.Commentf(out))
@@ -29,7 +29,7 @@ func (s *DockerRegistryAuthSuite) TestLoginToPrivateRegistry(c *check.C) {
 	dockerCmd(c, "login", "-u", s.reg.username, "-p", s.reg.password, privateRegistryURL)
 	dockerCmd(c, "login", "-u", s.reg.username, "-p", s.reg.password, privateRegistryURL)
 }
 }
 
 
-func (s *DockerRegistryAuthSuite) TestLoginToPrivateRegistryDeprecatedEmailFlag(c *check.C) {
+func (s *DockerRegistryAuthHtpasswdSuite) TestLoginToPrivateRegistryDeprecatedEmailFlag(c *check.C) {
 	// Test to make sure login still works with the deprecated -e and --email flags
 	// Test to make sure login still works with the deprecated -e and --email flags
 	// wrong credentials
 	// wrong credentials
 	out, _, err := dockerCmdWithError("login", "-u", s.reg.username, "-p", "WRONGPASSWORD", "-e", s.reg.email, privateRegistryURL)
 	out, _, err := dockerCmdWithError("login", "-u", s.reg.username, "-p", "WRONGPASSWORD", "-e", s.reg.email, privateRegistryURL)

+ 1 - 1
integration-cli/docker_cli_logout_test.go

@@ -10,7 +10,7 @@ import (
 	"github.com/go-check/check"
 	"github.com/go-check/check"
 )
 )
 
 
-func (s *DockerRegistryAuthSuite) TestLogoutWithExternalAuth(c *check.C) {
+func (s *DockerRegistryAuthHtpasswdSuite) TestLogoutWithExternalAuth(c *check.C) {
 	osPath := os.Getenv("PATH")
 	osPath := os.Getenv("PATH")
 	defer os.Setenv("PATH", osPath)
 	defer os.Setenv("PATH", osPath)
 
 

+ 1 - 1
integration-cli/docker_cli_pull_local_test.go

@@ -387,7 +387,7 @@ func (s *DockerRegistrySuite) TestPullManifestList(c *check.C) {
 	dockerCmd(c, "rmi", repoName)
 	dockerCmd(c, "rmi", repoName)
 }
 }
 
 
-func (s *DockerRegistryAuthSuite) TestPullWithExternalAuth(c *check.C) {
+func (s *DockerRegistryAuthHtpasswdSuite) TestPullWithExternalAuth(c *check.C) {
 	osPath := os.Getenv("PATH")
 	osPath := os.Getenv("PATH")
 	defer os.Setenv("PATH", osPath)
 	defer os.Setenv("PATH", osPath)
 
 

+ 1 - 1
integration-cli/docker_cli_pull_test.go

@@ -255,7 +255,7 @@ func (s *DockerHubPullSuite) TestPullClientDisconnect(c *check.C) {
 	c.Assert(err, checker.NotNil, check.Commentf("image was pulled after client disconnected"))
 	c.Assert(err, checker.NotNil, check.Commentf("image was pulled after client disconnected"))
 }
 }
 
 
-func (s *DockerRegistryAuthSuite) TestPullNoCredentialsNotFound(c *check.C) {
+func (s *DockerRegistryAuthHtpasswdSuite) TestPullNoCredentialsNotFound(c *check.C) {
 	// we don't care about the actual image, we just want to see image not found
 	// we don't care about the actual image, we just want to see image not found
 	// because that means v2 call returned 401 and we fell back to v1 which usually
 	// because that means v2 call returned 401 and we fell back to v1 which usually
 	// gives a 404 (in this case the test registry doesn't handle v1 at all)
 	// gives a 404 (in this case the test registry doesn't handle v1 at all)

+ 33 - 1
integration-cli/docker_cli_push_test.go

@@ -4,6 +4,8 @@ import (
 	"archive/tar"
 	"archive/tar"
 	"fmt"
 	"fmt"
 	"io/ioutil"
 	"io/ioutil"
+	"net/http"
+	"net/http/httptest"
 	"os"
 	"os"
 	"os/exec"
 	"os/exec"
 	"path/filepath"
 	"path/filepath"
@@ -528,7 +530,7 @@ func (s *DockerTrustSuite) TestTrustedPushWithReleasesDelegation(c *check.C) {
 	c.Assert(string(contents), checker.Contains, `"latest"`, check.Commentf(string(contents)))
 	c.Assert(string(contents), checker.Contains, `"latest"`, check.Commentf(string(contents)))
 }
 }
 
 
-func (s *DockerRegistryAuthSuite) TestPushNoCredentialsNoRetry(c *check.C) {
+func (s *DockerRegistryAuthHtpasswdSuite) TestPushNoCredentialsNoRetry(c *check.C) {
 	repoName := fmt.Sprintf("%s/busybox", privateRegistryURL)
 	repoName := fmt.Sprintf("%s/busybox", privateRegistryURL)
 	dockerCmd(c, "tag", "busybox", repoName)
 	dockerCmd(c, "tag", "busybox", repoName)
 	out, _, err := dockerCmdWithError("push", repoName)
 	out, _, err := dockerCmdWithError("push", repoName)
@@ -546,3 +548,33 @@ func (s *DockerSuite) TestPushToCentralRegistryUnauthorized(c *check.C) {
 	c.Assert(err, check.NotNil, check.Commentf(out))
 	c.Assert(err, check.NotNil, check.Commentf(out))
 	c.Assert(out, checker.Contains, "unauthorized: access to the requested resource is not authorized")
 	c.Assert(out, checker.Contains, "unauthorized: access to the requested resource is not authorized")
 }
 }
+
+func (s *DockerRegistryAuthTokenSuite) TestPushTokenServiceUnauthResponse(c *check.C) {
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		w.WriteHeader(http.StatusUnauthorized)
+		w.Header().Set("Content-Type", "application/json")
+		w.Write([]byte(`{"errors": [{"Code":"UNAUTHORIZED", "message": "a message", "detail": null}]}`))
+	}))
+	defer ts.Close()
+	s.setupRegistryWithTokenService(c, ts.URL)
+	repoName := fmt.Sprintf("%s/busybox", privateRegistryURL)
+	dockerCmd(c, "tag", "busybox", repoName)
+	out, _, err := dockerCmdWithError("push", repoName)
+	c.Assert(err, check.NotNil, check.Commentf(out))
+	c.Assert(out, checker.Contains, "unauthorized: a message")
+}
+
+func (s *DockerRegistryAuthTokenSuite) TestPushMisconfiguredTokenServiceResponse(c *check.C) {
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		w.WriteHeader(http.StatusUnauthorized)
+		w.Header().Set("Content-Type", "application/json")
+		// this will make the daemon panics if no check is performed in retryOnError
+		w.Write([]byte(`{"error": "unauthorized"}`))
+	}))
+	defer ts.Close()
+	s.setupRegistryWithTokenService(c, ts.URL)
+	repoName := fmt.Sprintf("%s/busybox", privateRegistryURL)
+	dockerCmd(c, "tag", "busybox", repoName)
+	out, _, err := dockerCmdWithError("push", repoName)
+	c.Assert(err, check.NotNil, check.Commentf(out))
+}

+ 2 - 2
integration-cli/docker_utils.go

@@ -1237,8 +1237,8 @@ func daemonTime(c *check.C) time.Time {
 	return dt
 	return dt
 }
 }
 
 
-func setupRegistry(c *check.C, schema1, auth bool) *testRegistryV2 {
-	reg, err := newTestRegistryV2(c, schema1, auth)
+func setupRegistry(c *check.C, schema1 bool, auth, tokenURL string) *testRegistryV2 {
+	reg, err := newTestRegistryV2(c, schema1, auth, tokenURL)
 	c.Assert(err, check.IsNil)
 	c.Assert(err, check.IsNil)
 
 
 	// Wait for registry to be ready to serve requests.
 	// Wait for registry to be ready to serve requests.

+ 21 - 0
integration-cli/fixtures/registry/cert.pem

@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDfzCCAmegAwIBAgIJAKZjzF7N4zFJMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV
+BAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQg
+Q29tcGFueSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xNjAzMTQxOTAzMDZa
+Fw0xNzAzMTQxOTAzMDZaMFYxCzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0
+IENpdHkxHDAaBgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQxEjAQBgNVBAMMCWxv
+Y2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMAVEPA6tSNy
+MoExHvT8CWvbe0MyYqZjMmUUdGVYyAaoZgmj9HvtGKaUWY/hCtgTond3OKhPq69u
+fQSDlHQA/scq4KZovKQJhvBaRb2DqD31KcbcDyh5KUAL1aalbjTLbKmAYSFSoY93
+57KiBei2BmvS55HLhOiO8ccQOq3feH/J/XcszAdAaiGXW3woDOIumYzur6Q8Suyn
+cIUEX5Ik7mxS7oGYN1IM++Y+B6aAFT7htAZEvF7RF7sjG7QBfxNPOFg9lBWXzVSv
+0vRbVme9OCDD2QOpj8O7XAPuLDwW5b2A8Iex3CJRngBI9vAK5h1Wssst8117bur9
+AiubOrF6cxUCAwEAAaNQME4wHQYDVR0OBBYEFNTGYK7uX19yjCPeGXhmel98amoA
+MB8GA1UdIwQYMBaAFNTGYK7uX19yjCPeGXhmel98amoAMAwGA1UdEwQFMAMBAf8w
+DQYJKoZIhvcNAQELBQADggEBACW/oF6RgLbTPxb8oPI9424Uv/erYYdxdqIaO3Mz
+fQfBEvGu62A0ZLH+av4BTeqBM6iVhN6/Y3hUb8UzbbZAIo/dVJSglW7PXAfUITMM
+ca9U2r2cFqgXELZkhde6mTFTYwM3swMCP0HUEo+Hu62NX5gunKr4QMNfTlE3vHEj
+jitnkTR0ZVEKHvmdTJC9S92j+NuaJVcwe5UNP1Nj/Ksd/iUUCa2DBnw2N7YwHTDB
+jb9cQb8aNVNSrjKP3sknMslVy1JVbUB1LXsth/h+kkVFNP4dsk+dZHn20uIA/VeJ
+mJ3Wo54CeTAa3DysiWbIIYsFSASCPvki08ZKI373tCf2RvE=
+-----END CERTIFICATE-----

+ 20 - 9
integration-cli/registry.go

@@ -20,12 +20,13 @@ const (
 type testRegistryV2 struct {
 type testRegistryV2 struct {
 	cmd      *exec.Cmd
 	cmd      *exec.Cmd
 	dir      string
 	dir      string
+	auth     string
 	username string
 	username string
 	password string
 	password string
 	email    string
 	email    string
 }
 }
 
 
-func newTestRegistryV2(c *check.C, schema1, auth bool) (*testRegistryV2, error) {
+func newTestRegistryV2(c *check.C, schema1 bool, auth, tokenURL string) (*testRegistryV2, error) {
 	tmp, err := ioutil.TempDir("", "registry-test-")
 	tmp, err := ioutil.TempDir("", "registry-test-")
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -39,12 +40,13 @@ http:
     addr: %s
     addr: %s
 %s`
 %s`
 	var (
 	var (
-		htpasswd string
-		username string
-		password string
-		email    string
+		authTemplate string
+		username     string
+		password     string
+		email        string
 	)
 	)
-	if auth {
+	switch auth {
+	case "htpasswd":
 		htpasswdPath := filepath.Join(tmp, "htpasswd")
 		htpasswdPath := filepath.Join(tmp, "htpasswd")
 		// generated with: htpasswd -Bbn testuser testpassword
 		// generated with: htpasswd -Bbn testuser testpassword
 		userpasswd := "testuser:$2y$05$sBsSqk0OpSD1uTZkHXc4FeJ0Z70wLQdAX/82UiHuQOKbNbBrzs63m"
 		userpasswd := "testuser:$2y$05$sBsSqk0OpSD1uTZkHXc4FeJ0Z70wLQdAX/82UiHuQOKbNbBrzs63m"
@@ -54,11 +56,19 @@ http:
 		if err := ioutil.WriteFile(htpasswdPath, []byte(userpasswd), os.FileMode(0644)); err != nil {
 		if err := ioutil.WriteFile(htpasswdPath, []byte(userpasswd), os.FileMode(0644)); err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
-		htpasswd = fmt.Sprintf(`auth:
+		authTemplate = fmt.Sprintf(`auth:
     htpasswd:
     htpasswd:
         realm: basic-realm
         realm: basic-realm
         path: %s
         path: %s
 `, htpasswdPath)
 `, htpasswdPath)
+	case "token":
+		authTemplate = fmt.Sprintf(`auth:
+    token:
+        realm: %s
+        service: "registry"
+        issuer: "auth-registry"
+        rootcertbundle: "fixtures/registry/cert.pem"
+`, tokenURL)
 	}
 	}
 
 
 	confPath := filepath.Join(tmp, "config.yaml")
 	confPath := filepath.Join(tmp, "config.yaml")
@@ -66,7 +76,7 @@ http:
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-	if _, err := fmt.Fprintf(config, template, tmp, privateRegistryURL, htpasswd); err != nil {
+	if _, err := fmt.Fprintf(config, template, tmp, privateRegistryURL, authTemplate); err != nil {
 		os.RemoveAll(tmp)
 		os.RemoveAll(tmp)
 		return nil, err
 		return nil, err
 	}
 	}
@@ -86,6 +96,7 @@ http:
 	return &testRegistryV2{
 	return &testRegistryV2{
 		cmd:      cmd,
 		cmd:      cmd,
 		dir:      tmp,
 		dir:      tmp,
+		auth:     auth,
 		username: username,
 		username: username,
 		password: password,
 		password: password,
 		email:    email,
 		email:    email,
@@ -101,7 +112,7 @@ func (t *testRegistryV2) Ping() error {
 	resp.Body.Close()
 	resp.Body.Close()
 
 
 	fail := resp.StatusCode != http.StatusOK
 	fail := resp.StatusCode != http.StatusOK
-	if t.username != "" {
+	if t.auth != "" {
 		// unauthorized is a _good_ status when pinging v2/ and it needs auth
 		// unauthorized is a _good_ status when pinging v2/ and it needs auth
 		fail = fail && resp.StatusCode != http.StatusUnauthorized
 		fail = fail && resp.StatusCode != http.StatusUnauthorized
 	}
 	}