Browse Source

Add support for identity token with token handler

Use token handler options for initialization.
Update auth endpoint to set identity token in response.
Update credential store to match distribution interface changes.

Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
Derek McGowan 9 years ago
parent
commit
e896d1d7c4

+ 7 - 0
api/client/trust.go

@@ -107,6 +107,13 @@ func (scs simpleCredentialStore) Basic(u *url.URL) (string, string) {
 	return scs.auth.Username, scs.auth.Password
 	return scs.auth.Username, scs.auth.Password
 }
 }
 
 
+func (scs simpleCredentialStore) RefreshToken(u *url.URL, service string) string {
+	return scs.auth.IdentityToken
+}
+
+func (scs simpleCredentialStore) SetRefreshToken(*url.URL, string, string) {
+}
+
 // getNotaryRepository returns a NotaryRepository which stores all the
 // getNotaryRepository returns a NotaryRepository which stores all the
 // information needed to operate on a notary repository.
 // information needed to operate on a notary repository.
 // It creates a HTTP transport providing authentication support.
 // It creates a HTTP transport providing authentication support.

+ 1 - 1
api/server/router/system/backend.go

@@ -13,5 +13,5 @@ type Backend interface {
 	SystemVersion() types.Version
 	SystemVersion() types.Version
 	SubscribeToEvents(since, sinceNano int64, ef filters.Args) ([]events.Message, chan interface{})
 	SubscribeToEvents(since, sinceNano int64, ef filters.Args) ([]events.Message, chan interface{})
 	UnsubscribeFromEvents(chan interface{})
 	UnsubscribeFromEvents(chan interface{})
-	AuthenticateToRegistry(authConfig *types.AuthConfig) (string, error)
+	AuthenticateToRegistry(authConfig *types.AuthConfig) (string, string, error)
 }
 }

+ 3 - 2
api/server/router/system/system_routes.go

@@ -115,11 +115,12 @@ func (s *systemRouter) postAuth(ctx context.Context, w http.ResponseWriter, r *h
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	status, err := s.backend.AuthenticateToRegistry(config)
+	status, token, err := s.backend.AuthenticateToRegistry(config)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 	return httputils.WriteJSON(w, http.StatusOK, &types.AuthResponse{
 	return httputils.WriteJSON(w, http.StatusOK, &types.AuthResponse{
-		Status: status,
+		Status:        status,
+		IdentityToken: token,
 	})
 	})
 }
 }

+ 1 - 1
daemon/daemon.go

@@ -1518,7 +1518,7 @@ func configureVolumes(config *Config, rootUID, rootGID int) (*store.VolumeStore,
 }
 }
 
 
 // AuthenticateToRegistry checks the validity of credentials in authConfig
 // AuthenticateToRegistry checks the validity of credentials in authConfig
-func (daemon *Daemon) AuthenticateToRegistry(authConfig *types.AuthConfig) (string, error) {
+func (daemon *Daemon) AuthenticateToRegistry(authConfig *types.AuthConfig) (string, string, error) {
 	return daemon.RegistryService.Auth(authConfig, dockerversion.DockerUserAgent())
 	return daemon.RegistryService.Auth(authConfig, dockerversion.DockerUserAgent())
 }
 }
 
 

+ 19 - 1
distribution/registry.go

@@ -26,6 +26,13 @@ func (dcs dumbCredentialStore) Basic(*url.URL) (string, string) {
 	return dcs.auth.Username, dcs.auth.Password
 	return dcs.auth.Username, dcs.auth.Password
 }
 }
 
 
+func (dcs dumbCredentialStore) RefreshToken(*url.URL, string) string {
+	return dcs.auth.IdentityToken
+}
+
+func (dcs dumbCredentialStore) SetRefreshToken(*url.URL, string, string) {
+}
+
 // NewV2Repository returns a repository (v2 only). It creates a HTTP transport
 // NewV2Repository returns a repository (v2 only). It creates a HTTP transport
 // providing timeout settings and authentication support, and also verifies the
 // providing timeout settings and authentication support, and also verifies the
 // remote API version.
 // remote API version.
@@ -72,7 +79,18 @@ func NewV2Repository(ctx context.Context, repoInfo *registry.RepositoryInfo, end
 		modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, passThruTokenHandler))
 		modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, passThruTokenHandler))
 	} else {
 	} else {
 		creds := dumbCredentialStore{auth: authConfig}
 		creds := dumbCredentialStore{auth: authConfig}
-		tokenHandler := auth.NewTokenHandler(authTransport, creds, repoName, actions...)
+		tokenHandlerOptions := auth.TokenHandlerOptions{
+			Transport:   authTransport,
+			Credentials: creds,
+			Scopes: []auth.Scope{
+				auth.RepositoryScope{
+					Repository: repoName,
+					Actions:    actions,
+				},
+			},
+			ClientID: registry.AuthClientID,
+		}
+		tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions)
 		basicHandler := auth.NewBasicHandler(creds)
 		basicHandler := auth.NewBasicHandler(creds)
 		modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))
 		modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))
 	}
 	}

+ 42 - 23
registry/auth.go

@@ -15,11 +15,16 @@ import (
 	registrytypes "github.com/docker/engine-api/types/registry"
 	registrytypes "github.com/docker/engine-api/types/registry"
 )
 )
 
 
+const (
+	// AuthClientID is used the ClientID used for the token server
+	AuthClientID = "docker"
+)
+
 // loginV1 tries to register/login to the v1 registry server.
 // loginV1 tries to register/login to the v1 registry server.
-func loginV1(authConfig *types.AuthConfig, apiEndpoint APIEndpoint, userAgent string) (string, error) {
+func loginV1(authConfig *types.AuthConfig, apiEndpoint APIEndpoint, userAgent string) (string, string, error) {
 	registryEndpoint, err := apiEndpoint.ToV1Endpoint(userAgent, nil)
 	registryEndpoint, err := apiEndpoint.ToV1Endpoint(userAgent, nil)
 	if err != nil {
 	if err != nil {
-		return "", err
+		return "", "", err
 	}
 	}
 
 
 	serverAddress := registryEndpoint.String()
 	serverAddress := registryEndpoint.String()
@@ -27,48 +32,47 @@ func loginV1(authConfig *types.AuthConfig, apiEndpoint APIEndpoint, userAgent st
 	logrus.Debugf("attempting v1 login to registry endpoint %s", registryEndpoint)
 	logrus.Debugf("attempting v1 login to registry endpoint %s", registryEndpoint)
 
 
 	if serverAddress == "" {
 	if serverAddress == "" {
-		return "", fmt.Errorf("Server Error: Server Address not set.")
+		return "", "", fmt.Errorf("Server Error: Server Address not set.")
 	}
 	}
 
 
 	loginAgainstOfficialIndex := serverAddress == IndexServer
 	loginAgainstOfficialIndex := serverAddress == IndexServer
 
 
 	req, err := http.NewRequest("GET", serverAddress+"users/", nil)
 	req, err := http.NewRequest("GET", serverAddress+"users/", nil)
 	if err != nil {
 	if err != nil {
-		return "", err
+		return "", "", err
 	}
 	}
 	req.SetBasicAuth(authConfig.Username, authConfig.Password)
 	req.SetBasicAuth(authConfig.Username, authConfig.Password)
 	resp, err := registryEndpoint.client.Do(req)
 	resp, err := registryEndpoint.client.Do(req)
 	if err != nil {
 	if err != nil {
 		// fallback when request could not be completed
 		// fallback when request could not be completed
-		return "", fallbackError{
+		return "", "", fallbackError{
 			err: err,
 			err: err,
 		}
 		}
 	}
 	}
 	defer resp.Body.Close()
 	defer resp.Body.Close()
 	body, err := ioutil.ReadAll(resp.Body)
 	body, err := ioutil.ReadAll(resp.Body)
 	if err != nil {
 	if err != nil {
-		return "", err
+		return "", "", err
 	}
 	}
 	if resp.StatusCode == http.StatusOK {
 	if resp.StatusCode == http.StatusOK {
-		return "Login Succeeded", nil
+		return "Login Succeeded", "", nil
 	} else if resp.StatusCode == http.StatusUnauthorized {
 	} else if resp.StatusCode == http.StatusUnauthorized {
 		if loginAgainstOfficialIndex {
 		if loginAgainstOfficialIndex {
-			return "", fmt.Errorf("Wrong login/password, please try again. Haven't got a Docker ID? Create one at https://hub.docker.com")
+			return "", "", fmt.Errorf("Wrong login/password, please try again. Haven't got a Docker ID? Create one at https://hub.docker.com")
 		}
 		}
-		return "", fmt.Errorf("Wrong login/password, please try again")
+		return "", "", fmt.Errorf("Wrong login/password, please try again")
 	} else if resp.StatusCode == http.StatusForbidden {
 	} else if resp.StatusCode == http.StatusForbidden {
 		if loginAgainstOfficialIndex {
 		if loginAgainstOfficialIndex {
-			return "", fmt.Errorf("Login: Account is not active. Please check your e-mail for a confirmation link.")
+			return "", "", fmt.Errorf("Login: Account is not active. Please check your e-mail for a confirmation link.")
 		}
 		}
 		// *TODO: Use registry configuration to determine what this says, if anything?
 		// *TODO: Use registry configuration to determine what this says, if anything?
-		return "", fmt.Errorf("Login: Account is not active. Please see the documentation of the registry %s for instructions how to activate it.", serverAddress)
+		return "", "", fmt.Errorf("Login: Account is not active. Please see the documentation of the registry %s for instructions how to activate it.", serverAddress)
 	} else if resp.StatusCode == http.StatusInternalServerError { // Issue #14326
 	} else if resp.StatusCode == http.StatusInternalServerError { // Issue #14326
 		logrus.Errorf("%s returned status code %d. Response Body :\n%s", req.URL.String(), resp.StatusCode, body)
 		logrus.Errorf("%s returned status code %d. Response Body :\n%s", req.URL.String(), resp.StatusCode, body)
-		return "", fmt.Errorf("Internal Server Error")
-	} else {
-		return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body,
-			resp.StatusCode, resp.Header)
+		return "", "", fmt.Errorf("Internal Server Error")
 	}
 	}
+	return "", "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body,
+		resp.StatusCode, resp.Header)
 }
 }
 
 
 type loginCredentialStore struct {
 type loginCredentialStore struct {
@@ -79,6 +83,14 @@ func (lcs loginCredentialStore) Basic(*url.URL) (string, string) {
 	return lcs.authConfig.Username, lcs.authConfig.Password
 	return lcs.authConfig.Username, lcs.authConfig.Password
 }
 }
 
 
+func (lcs loginCredentialStore) RefreshToken(*url.URL, string) string {
+	return lcs.authConfig.IdentityToken
+}
+
+func (lcs loginCredentialStore) SetRefreshToken(u *url.URL, service, token string) {
+	lcs.authConfig.IdentityToken = token
+}
+
 type fallbackError struct {
 type fallbackError struct {
 	err error
 	err error
 }
 }
@@ -90,7 +102,7 @@ func (err fallbackError) Error() string {
 // loginV2 tries to login to the v2 registry server. The given registry
 // loginV2 tries to login to the v2 registry server. The given registry
 // endpoint will be pinged to get authorization challenges. These challenges
 // endpoint will be pinged to get authorization challenges. These challenges
 // will be used to authenticate against the registry to validate credentials.
 // will be used to authenticate against the registry to validate credentials.
-func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent string) (string, error) {
+func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent string) (string, string, error) {
 	logrus.Debugf("attempting v2 login to registry endpoint %s", endpoint)
 	logrus.Debugf("attempting v2 login to registry endpoint %s", endpoint)
 
 
 	modifiers := DockerHeaders(userAgent, nil)
 	modifiers := DockerHeaders(userAgent, nil)
@@ -101,14 +113,21 @@ func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent strin
 		if !foundV2 {
 		if !foundV2 {
 			err = fallbackError{err: err}
 			err = fallbackError{err: err}
 		}
 		}
-		return "", err
+		return "", "", err
 	}
 	}
 
 
+	credentialAuthConfig := *authConfig
 	creds := loginCredentialStore{
 	creds := loginCredentialStore{
-		authConfig: authConfig,
+		authConfig: &credentialAuthConfig,
 	}
 	}
 
 
-	tokenHandler := auth.NewTokenHandler(authTransport, creds, "")
+	tokenHandlerOptions := auth.TokenHandlerOptions{
+		Transport:     authTransport,
+		Credentials:   creds,
+		OfflineAccess: true,
+		ClientID:      AuthClientID,
+	}
+	tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions)
 	basicHandler := auth.NewBasicHandler(creds)
 	basicHandler := auth.NewBasicHandler(creds)
 	modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))
 	modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))
 	tr := transport.NewTransport(authTransport, modifiers...)
 	tr := transport.NewTransport(authTransport, modifiers...)
@@ -124,7 +143,7 @@ func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent strin
 		if !foundV2 {
 		if !foundV2 {
 			err = fallbackError{err: err}
 			err = fallbackError{err: err}
 		}
 		}
-		return "", err
+		return "", "", err
 	}
 	}
 
 
 	resp, err := loginClient.Do(req)
 	resp, err := loginClient.Do(req)
@@ -132,7 +151,7 @@ func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent strin
 		if !foundV2 {
 		if !foundV2 {
 			err = fallbackError{err: err}
 			err = fallbackError{err: err}
 		}
 		}
-		return "", err
+		return "", "", err
 	}
 	}
 	defer resp.Body.Close()
 	defer resp.Body.Close()
 
 
@@ -142,10 +161,10 @@ func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent strin
 		if !foundV2 {
 		if !foundV2 {
 			err = fallbackError{err: err}
 			err = fallbackError{err: err}
 		}
 		}
-		return "", err
+		return "", "", err
 	}
 	}
 
 
-	return "Login Succeeded", nil
+	return "Login Succeeded", credentialAuthConfig.IdentityToken, nil
 
 
 }
 }
 
 

+ 16 - 6
registry/service.go

@@ -2,6 +2,7 @@ package registry
 
 
 import (
 import (
 	"crypto/tls"
 	"crypto/tls"
+	"fmt"
 	"net/http"
 	"net/http"
 	"net/url"
 	"net/url"
 	"strings"
 	"strings"
@@ -29,10 +30,19 @@ func NewService(options *Options) *Service {
 // Auth contacts the public registry with the provided credentials,
 // Auth contacts the public registry with the provided credentials,
 // and returns OK if authentication was successful.
 // and returns OK if authentication was successful.
 // It can be used to verify the validity of a client's credentials.
 // It can be used to verify the validity of a client's credentials.
-func (s *Service) Auth(authConfig *types.AuthConfig, userAgent string) (status string, err error) {
-	endpoints, err := s.LookupPushEndpoints(authConfig.ServerAddress)
+func (s *Service) Auth(authConfig *types.AuthConfig, userAgent string) (status, token string, err error) {
+	serverAddress := authConfig.ServerAddress
+	if !strings.HasPrefix(serverAddress, "https://") && !strings.HasPrefix(serverAddress, "http://") {
+		serverAddress = "https://" + serverAddress
+	}
+	u, err := url.Parse(serverAddress)
+	if err != nil {
+		return "", "", fmt.Errorf("unable to parse server address: %v", err)
+	}
+
+	endpoints, err := s.LookupPushEndpoints(u.Host)
 	if err != nil {
 	if err != nil {
-		return "", err
+		return "", "", err
 	}
 	}
 
 
 	for _, endpoint := range endpoints {
 	for _, endpoint := range endpoints {
@@ -41,7 +51,7 @@ func (s *Service) Auth(authConfig *types.AuthConfig, userAgent string) (status s
 			login = loginV1
 			login = loginV1
 		}
 		}
 
 
-		status, err = login(authConfig, endpoint, userAgent)
+		status, token, err = login(authConfig, endpoint, userAgent)
 		if err == nil {
 		if err == nil {
 			return
 			return
 		}
 		}
@@ -50,10 +60,10 @@ func (s *Service) Auth(authConfig *types.AuthConfig, userAgent string) (status s
 			logrus.Infof("Error logging in to %s endpoint, trying next endpoint: %v", endpoint.Version, err)
 			logrus.Infof("Error logging in to %s endpoint, trying next endpoint: %v", endpoint.Version, err)
 			continue
 			continue
 		}
 		}
-		return "", err
+		return "", "", err
 	}
 	}
 
 
-	return "", err
+	return "", "", err
 }
 }
 
 
 // splitReposSearchTerm breaks a search term into an index name and remote name
 // splitReposSearchTerm breaks a search term into an index name and remote name

+ 1 - 1
registry/service_v2.go

@@ -10,7 +10,7 @@ import (
 func (s *Service) lookupV2Endpoints(hostname string) (endpoints []APIEndpoint, err error) {
 func (s *Service) lookupV2Endpoints(hostname string) (endpoints []APIEndpoint, err error) {
 	var cfg = tlsconfig.ServerDefault
 	var cfg = tlsconfig.ServerDefault
 	tlsConfig := &cfg
 	tlsConfig := &cfg
-	if hostname == DefaultNamespace {
+	if hostname == DefaultNamespace || hostname == DefaultV1Registry.Host {
 		// v2 mirrors
 		// v2 mirrors
 		for _, mirror := range s.Config.Mirrors {
 		for _, mirror := range s.Config.Mirrors {
 			if !strings.HasPrefix(mirror, "http://") && !strings.HasPrefix(mirror, "https://") {
 			if !strings.HasPrefix(mirror, "http://") && !strings.HasPrefix(mirror, "https://") {