From e896d1d7c4459c4b357efdd780e9fb9dd9bc90e0 Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Tue, 23 Feb 2016 15:18:04 -0800 Subject: [PATCH] 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 (github: dmcgowan) --- api/client/trust.go | 7 +++ api/server/router/system/backend.go | 2 +- api/server/router/system/system_routes.go | 5 +- daemon/daemon.go | 2 +- distribution/registry.go | 20 ++++++- registry/auth.go | 65 +++++++++++++++-------- registry/service.go | 22 +++++--- registry/service_v2.go | 2 +- 8 files changed, 90 insertions(+), 35 deletions(-) diff --git a/api/client/trust.go b/api/client/trust.go index 88cfb3244b..e61c8225b6 100644 --- a/api/client/trust.go +++ b/api/client/trust.go @@ -107,6 +107,13 @@ func (scs simpleCredentialStore) Basic(u *url.URL) (string, string) { 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 // information needed to operate on a notary repository. // It creates a HTTP transport providing authentication support. diff --git a/api/server/router/system/backend.go b/api/server/router/system/backend.go index 8a270027ff..c842ce68cc 100644 --- a/api/server/router/system/backend.go +++ b/api/server/router/system/backend.go @@ -13,5 +13,5 @@ type Backend interface { SystemVersion() types.Version SubscribeToEvents(since, sinceNano int64, ef filters.Args) ([]events.Message, chan interface{}) UnsubscribeFromEvents(chan interface{}) - AuthenticateToRegistry(authConfig *types.AuthConfig) (string, error) + AuthenticateToRegistry(authConfig *types.AuthConfig) (string, string, error) } diff --git a/api/server/router/system/system_routes.go b/api/server/router/system/system_routes.go index 41b531129d..1819747f7d 100644 --- a/api/server/router/system/system_routes.go +++ b/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 { return err } - status, err := s.backend.AuthenticateToRegistry(config) + status, token, err := s.backend.AuthenticateToRegistry(config) if err != nil { return err } return httputils.WriteJSON(w, http.StatusOK, &types.AuthResponse{ - Status: status, + Status: status, + IdentityToken: token, }) } diff --git a/daemon/daemon.go b/daemon/daemon.go index 892e83dac0..e1e487d8c1 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1518,7 +1518,7 @@ func configureVolumes(config *Config, rootUID, rootGID int) (*store.VolumeStore, } // 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()) } diff --git a/distribution/registry.go b/distribution/registry.go index 4946c0b573..a4de299530 100644 --- a/distribution/registry.go +++ b/distribution/registry.go @@ -26,6 +26,13 @@ func (dcs dumbCredentialStore) Basic(*url.URL) (string, string) { 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 // providing timeout settings and authentication support, and also verifies the // remote API version. @@ -72,7 +79,18 @@ func NewV2Repository(ctx context.Context, repoInfo *registry.RepositoryInfo, end modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, passThruTokenHandler)) } else { 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) modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)) } diff --git a/registry/auth.go b/registry/auth.go index a8fdb675c1..8351cd91c3 100644 --- a/registry/auth.go +++ b/registry/auth.go @@ -15,11 +15,16 @@ import ( 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. -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) if err != nil { - return "", err + return "", "", err } 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) if serverAddress == "" { - return "", fmt.Errorf("Server Error: Server Address not set.") + return "", "", fmt.Errorf("Server Error: Server Address not set.") } loginAgainstOfficialIndex := serverAddress == IndexServer req, err := http.NewRequest("GET", serverAddress+"users/", nil) if err != nil { - return "", err + return "", "", err } req.SetBasicAuth(authConfig.Username, authConfig.Password) resp, err := registryEndpoint.client.Do(req) if err != nil { // fallback when request could not be completed - return "", fallbackError{ + return "", "", fallbackError{ err: err, } } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { - return "", err + return "", "", err } if resp.StatusCode == http.StatusOK { - return "Login Succeeded", nil + return "Login Succeeded", "", nil } else if resp.StatusCode == http.StatusUnauthorized { 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 { 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? - 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 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 { @@ -79,6 +83,14 @@ func (lcs loginCredentialStore) Basic(*url.URL) (string, string) { 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 { err error } @@ -90,7 +102,7 @@ func (err fallbackError) Error() string { // loginV2 tries to login to the v2 registry server. The given registry // endpoint will be pinged to get authorization challenges. These challenges // 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) modifiers := DockerHeaders(userAgent, nil) @@ -101,14 +113,21 @@ func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent strin if !foundV2 { err = fallbackError{err: err} } - return "", err + return "", "", err } + credentialAuthConfig := *authConfig 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) modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)) tr := transport.NewTransport(authTransport, modifiers...) @@ -124,7 +143,7 @@ func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent strin if !foundV2 { err = fallbackError{err: err} } - return "", err + return "", "", err } resp, err := loginClient.Do(req) @@ -132,7 +151,7 @@ func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent strin if !foundV2 { err = fallbackError{err: err} } - return "", err + return "", "", err } defer resp.Body.Close() @@ -142,10 +161,10 @@ func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent strin if !foundV2 { err = fallbackError{err: err} } - return "", err + return "", "", err } - return "Login Succeeded", nil + return "Login Succeeded", credentialAuthConfig.IdentityToken, nil } diff --git a/registry/service.go b/registry/service.go index 2124da6d9f..830c2bf69a 100644 --- a/registry/service.go +++ b/registry/service.go @@ -2,6 +2,7 @@ package registry import ( "crypto/tls" + "fmt" "net/http" "net/url" "strings" @@ -29,10 +30,19 @@ func NewService(options *Options) *Service { // Auth contacts the public registry with the provided credentials, // and returns OK if authentication was successful. // 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 "", err + return "", "", fmt.Errorf("unable to parse server address: %v", err) + } + + endpoints, err := s.LookupPushEndpoints(u.Host) + if err != nil { + return "", "", err } for _, endpoint := range endpoints { @@ -41,7 +51,7 @@ func (s *Service) Auth(authConfig *types.AuthConfig, userAgent string) (status s login = loginV1 } - status, err = login(authConfig, endpoint, userAgent) + status, token, err = login(authConfig, endpoint, userAgent) if err == nil { 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) continue } - return "", err + return "", "", err } - return "", err + return "", "", err } // splitReposSearchTerm breaks a search term into an index name and remote name diff --git a/registry/service_v2.go b/registry/service_v2.go index 9c909f186e..0c8f04c5a9 100644 --- a/registry/service_v2.go +++ b/registry/service_v2.go @@ -10,7 +10,7 @@ import ( func (s *Service) lookupV2Endpoints(hostname string) (endpoints []APIEndpoint, err error) { var cfg = tlsconfig.ServerDefault tlsConfig := &cfg - if hostname == DefaultNamespace { + if hostname == DefaultNamespace || hostname == DefaultV1Registry.Host { // v2 mirrors for _, mirror := range s.Config.Mirrors { if !strings.HasPrefix(mirror, "http://") && !strings.HasPrefix(mirror, "https://") {