Browse code

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 authored on 2016/02/24 08:18:04
Showing 8 changed files
... ...
@@ -107,6 +107,13 @@ func (scs simpleCredentialStore) Basic(u *url.URL) (string, string) {
107 107
 	return scs.auth.Username, scs.auth.Password
108 108
 }
109 109
 
110
+func (scs simpleCredentialStore) RefreshToken(u *url.URL, service string) string {
111
+	return scs.auth.IdentityToken
112
+}
113
+
114
+func (scs simpleCredentialStore) SetRefreshToken(*url.URL, string, string) {
115
+}
116
+
110 117
 // getNotaryRepository returns a NotaryRepository which stores all the
111 118
 // information needed to operate on a notary repository.
112 119
 // It creates a HTTP transport providing authentication support.
... ...
@@ -13,5 +13,5 @@ type Backend interface {
13 13
 	SystemVersion() types.Version
14 14
 	SubscribeToEvents(since, sinceNano int64, ef filters.Args) ([]events.Message, chan interface{})
15 15
 	UnsubscribeFromEvents(chan interface{})
16
-	AuthenticateToRegistry(authConfig *types.AuthConfig) (string, error)
16
+	AuthenticateToRegistry(authConfig *types.AuthConfig) (string, string, error)
17 17
 }
... ...
@@ -115,11 +115,12 @@ func (s *systemRouter) postAuth(ctx context.Context, w http.ResponseWriter, r *h
115 115
 	if err != nil {
116 116
 		return err
117 117
 	}
118
-	status, err := s.backend.AuthenticateToRegistry(config)
118
+	status, token, err := s.backend.AuthenticateToRegistry(config)
119 119
 	if err != nil {
120 120
 		return err
121 121
 	}
122 122
 	return httputils.WriteJSON(w, http.StatusOK, &types.AuthResponse{
123
-		Status: status,
123
+		Status:        status,
124
+		IdentityToken: token,
124 125
 	})
125 126
 }
... ...
@@ -1518,7 +1518,7 @@ func configureVolumes(config *Config, rootUID, rootGID int) (*store.VolumeStore,
1518 1518
 }
1519 1519
 
1520 1520
 // AuthenticateToRegistry checks the validity of credentials in authConfig
1521
-func (daemon *Daemon) AuthenticateToRegistry(authConfig *types.AuthConfig) (string, error) {
1521
+func (daemon *Daemon) AuthenticateToRegistry(authConfig *types.AuthConfig) (string, string, error) {
1522 1522
 	return daemon.RegistryService.Auth(authConfig, dockerversion.DockerUserAgent())
1523 1523
 }
1524 1524
 
... ...
@@ -26,6 +26,13 @@ func (dcs dumbCredentialStore) Basic(*url.URL) (string, string) {
26 26
 	return dcs.auth.Username, dcs.auth.Password
27 27
 }
28 28
 
29
+func (dcs dumbCredentialStore) RefreshToken(*url.URL, string) string {
30
+	return dcs.auth.IdentityToken
31
+}
32
+
33
+func (dcs dumbCredentialStore) SetRefreshToken(*url.URL, string, string) {
34
+}
35
+
29 36
 // NewV2Repository returns a repository (v2 only). It creates a HTTP transport
30 37
 // providing timeout settings and authentication support, and also verifies the
31 38
 // remote API version.
... ...
@@ -72,7 +79,18 @@ func NewV2Repository(ctx context.Context, repoInfo *registry.RepositoryInfo, end
72 72
 		modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, passThruTokenHandler))
73 73
 	} else {
74 74
 		creds := dumbCredentialStore{auth: authConfig}
75
-		tokenHandler := auth.NewTokenHandler(authTransport, creds, repoName, actions...)
75
+		tokenHandlerOptions := auth.TokenHandlerOptions{
76
+			Transport:   authTransport,
77
+			Credentials: creds,
78
+			Scopes: []auth.Scope{
79
+				auth.RepositoryScope{
80
+					Repository: repoName,
81
+					Actions:    actions,
82
+				},
83
+			},
84
+			ClientID: registry.AuthClientID,
85
+		}
86
+		tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions)
76 87
 		basicHandler := auth.NewBasicHandler(creds)
77 88
 		modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))
78 89
 	}
... ...
@@ -15,11 +15,16 @@ import (
15 15
 	registrytypes "github.com/docker/engine-api/types/registry"
16 16
 )
17 17
 
18
+const (
19
+	// AuthClientID is used the ClientID used for the token server
20
+	AuthClientID = "docker"
21
+)
22
+
18 23
 // loginV1 tries to register/login to the v1 registry server.
19
-func loginV1(authConfig *types.AuthConfig, apiEndpoint APIEndpoint, userAgent string) (string, error) {
24
+func loginV1(authConfig *types.AuthConfig, apiEndpoint APIEndpoint, userAgent string) (string, string, error) {
20 25
 	registryEndpoint, err := apiEndpoint.ToV1Endpoint(userAgent, nil)
21 26
 	if err != nil {
22
-		return "", err
27
+		return "", "", err
23 28
 	}
24 29
 
25 30
 	serverAddress := registryEndpoint.String()
... ...
@@ -27,48 +32,47 @@ func loginV1(authConfig *types.AuthConfig, apiEndpoint APIEndpoint, userAgent st
27 27
 	logrus.Debugf("attempting v1 login to registry endpoint %s", registryEndpoint)
28 28
 
29 29
 	if serverAddress == "" {
30
-		return "", fmt.Errorf("Server Error: Server Address not set.")
30
+		return "", "", fmt.Errorf("Server Error: Server Address not set.")
31 31
 	}
32 32
 
33 33
 	loginAgainstOfficialIndex := serverAddress == IndexServer
34 34
 
35 35
 	req, err := http.NewRequest("GET", serverAddress+"users/", nil)
36 36
 	if err != nil {
37
-		return "", err
37
+		return "", "", err
38 38
 	}
39 39
 	req.SetBasicAuth(authConfig.Username, authConfig.Password)
40 40
 	resp, err := registryEndpoint.client.Do(req)
41 41
 	if err != nil {
42 42
 		// fallback when request could not be completed
43
-		return "", fallbackError{
43
+		return "", "", fallbackError{
44 44
 			err: err,
45 45
 		}
46 46
 	}
47 47
 	defer resp.Body.Close()
48 48
 	body, err := ioutil.ReadAll(resp.Body)
49 49
 	if err != nil {
50
-		return "", err
50
+		return "", "", err
51 51
 	}
52 52
 	if resp.StatusCode == http.StatusOK {
53
-		return "Login Succeeded", nil
53
+		return "Login Succeeded", "", nil
54 54
 	} else if resp.StatusCode == http.StatusUnauthorized {
55 55
 		if loginAgainstOfficialIndex {
56
-			return "", fmt.Errorf("Wrong login/password, please try again. Haven't got a Docker ID? Create one at https://hub.docker.com")
56
+			return "", "", fmt.Errorf("Wrong login/password, please try again. Haven't got a Docker ID? Create one at https://hub.docker.com")
57 57
 		}
58
-		return "", fmt.Errorf("Wrong login/password, please try again")
58
+		return "", "", fmt.Errorf("Wrong login/password, please try again")
59 59
 	} else if resp.StatusCode == http.StatusForbidden {
60 60
 		if loginAgainstOfficialIndex {
61
-			return "", fmt.Errorf("Login: Account is not active. Please check your e-mail for a confirmation link.")
61
+			return "", "", fmt.Errorf("Login: Account is not active. Please check your e-mail for a confirmation link.")
62 62
 		}
63 63
 		// *TODO: Use registry configuration to determine what this says, if anything?
64
-		return "", fmt.Errorf("Login: Account is not active. Please see the documentation of the registry %s for instructions how to activate it.", serverAddress)
64
+		return "", "", fmt.Errorf("Login: Account is not active. Please see the documentation of the registry %s for instructions how to activate it.", serverAddress)
65 65
 	} else if resp.StatusCode == http.StatusInternalServerError { // Issue #14326
66 66
 		logrus.Errorf("%s returned status code %d. Response Body :\n%s", req.URL.String(), resp.StatusCode, body)
67
-		return "", fmt.Errorf("Internal Server Error")
68
-	} else {
69
-		return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body,
70
-			resp.StatusCode, resp.Header)
67
+		return "", "", fmt.Errorf("Internal Server Error")
71 68
 	}
69
+	return "", "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body,
70
+		resp.StatusCode, resp.Header)
72 71
 }
73 72
 
74 73
 type loginCredentialStore struct {
... ...
@@ -79,6 +83,14 @@ func (lcs loginCredentialStore) Basic(*url.URL) (string, string) {
79 79
 	return lcs.authConfig.Username, lcs.authConfig.Password
80 80
 }
81 81
 
82
+func (lcs loginCredentialStore) RefreshToken(*url.URL, string) string {
83
+	return lcs.authConfig.IdentityToken
84
+}
85
+
86
+func (lcs loginCredentialStore) SetRefreshToken(u *url.URL, service, token string) {
87
+	lcs.authConfig.IdentityToken = token
88
+}
89
+
82 90
 type fallbackError struct {
83 91
 	err error
84 92
 }
... ...
@@ -90,7 +102,7 @@ func (err fallbackError) Error() string {
90 90
 // loginV2 tries to login to the v2 registry server. The given registry
91 91
 // endpoint will be pinged to get authorization challenges. These challenges
92 92
 // will be used to authenticate against the registry to validate credentials.
93
-func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent string) (string, error) {
93
+func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent string) (string, string, error) {
94 94
 	logrus.Debugf("attempting v2 login to registry endpoint %s", endpoint)
95 95
 
96 96
 	modifiers := DockerHeaders(userAgent, nil)
... ...
@@ -101,14 +113,21 @@ func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent strin
101 101
 		if !foundV2 {
102 102
 			err = fallbackError{err: err}
103 103
 		}
104
-		return "", err
104
+		return "", "", err
105 105
 	}
106 106
 
107
+	credentialAuthConfig := *authConfig
107 108
 	creds := loginCredentialStore{
108
-		authConfig: authConfig,
109
+		authConfig: &credentialAuthConfig,
109 110
 	}
110 111
 
111
-	tokenHandler := auth.NewTokenHandler(authTransport, creds, "")
112
+	tokenHandlerOptions := auth.TokenHandlerOptions{
113
+		Transport:     authTransport,
114
+		Credentials:   creds,
115
+		OfflineAccess: true,
116
+		ClientID:      AuthClientID,
117
+	}
118
+	tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions)
112 119
 	basicHandler := auth.NewBasicHandler(creds)
113 120
 	modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))
114 121
 	tr := transport.NewTransport(authTransport, modifiers...)
... ...
@@ -124,7 +143,7 @@ func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent strin
124 124
 		if !foundV2 {
125 125
 			err = fallbackError{err: err}
126 126
 		}
127
-		return "", err
127
+		return "", "", err
128 128
 	}
129 129
 
130 130
 	resp, err := loginClient.Do(req)
... ...
@@ -132,7 +151,7 @@ func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent strin
132 132
 		if !foundV2 {
133 133
 			err = fallbackError{err: err}
134 134
 		}
135
-		return "", err
135
+		return "", "", err
136 136
 	}
137 137
 	defer resp.Body.Close()
138 138
 
... ...
@@ -142,10 +161,10 @@ func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent strin
142 142
 		if !foundV2 {
143 143
 			err = fallbackError{err: err}
144 144
 		}
145
-		return "", err
145
+		return "", "", err
146 146
 	}
147 147
 
148
-	return "Login Succeeded", nil
148
+	return "Login Succeeded", credentialAuthConfig.IdentityToken, nil
149 149
 
150 150
 }
151 151
 
... ...
@@ -2,6 +2,7 @@ package registry
2 2
 
3 3
 import (
4 4
 	"crypto/tls"
5
+	"fmt"
5 6
 	"net/http"
6 7
 	"net/url"
7 8
 	"strings"
... ...
@@ -29,10 +30,19 @@ func NewService(options *Options) *Service {
29 29
 // Auth contacts the public registry with the provided credentials,
30 30
 // and returns OK if authentication was successful.
31 31
 // It can be used to verify the validity of a client's credentials.
32
-func (s *Service) Auth(authConfig *types.AuthConfig, userAgent string) (status string, err error) {
33
-	endpoints, err := s.LookupPushEndpoints(authConfig.ServerAddress)
32
+func (s *Service) Auth(authConfig *types.AuthConfig, userAgent string) (status, token string, err error) {
33
+	serverAddress := authConfig.ServerAddress
34
+	if !strings.HasPrefix(serverAddress, "https://") && !strings.HasPrefix(serverAddress, "http://") {
35
+		serverAddress = "https://" + serverAddress
36
+	}
37
+	u, err := url.Parse(serverAddress)
38
+	if err != nil {
39
+		return "", "", fmt.Errorf("unable to parse server address: %v", err)
40
+	}
41
+
42
+	endpoints, err := s.LookupPushEndpoints(u.Host)
34 43
 	if err != nil {
35
-		return "", err
44
+		return "", "", err
36 45
 	}
37 46
 
38 47
 	for _, endpoint := range endpoints {
... ...
@@ -41,7 +51,7 @@ func (s *Service) Auth(authConfig *types.AuthConfig, userAgent string) (status s
41 41
 			login = loginV1
42 42
 		}
43 43
 
44
-		status, err = login(authConfig, endpoint, userAgent)
44
+		status, token, err = login(authConfig, endpoint, userAgent)
45 45
 		if err == nil {
46 46
 			return
47 47
 		}
... ...
@@ -50,10 +60,10 @@ func (s *Service) Auth(authConfig *types.AuthConfig, userAgent string) (status s
50 50
 			logrus.Infof("Error logging in to %s endpoint, trying next endpoint: %v", endpoint.Version, err)
51 51
 			continue
52 52
 		}
53
-		return "", err
53
+		return "", "", err
54 54
 	}
55 55
 
56
-	return "", err
56
+	return "", "", err
57 57
 }
58 58
 
59 59
 // splitReposSearchTerm breaks a search term into an index name and remote name
... ...
@@ -10,7 +10,7 @@ import (
10 10
 func (s *Service) lookupV2Endpoints(hostname string) (endpoints []APIEndpoint, err error) {
11 11
 	var cfg = tlsconfig.ServerDefault
12 12
 	tlsConfig := &cfg
13
-	if hostname == DefaultNamespace {
13
+	if hostname == DefaultNamespace || hostname == DefaultV1Registry.Host {
14 14
 		// v2 mirrors
15 15
 		for _, mirror := range s.Config.Mirrors {
16 16
 			if !strings.HasPrefix(mirror, "http://") && !strings.HasPrefix(mirror, "https://") {