Browse code

Adds support for v2 registry login

summary of changes:

registry/auth.go
- More logging around the login functions
- split Login() out to handle different code paths for v1 (unchanged logic)
and v2 (does not currently do account creation)
- handling for either basic or token based login attempts
registry/authchallenge.go
- New File
- credit to Brian Bland <brian.bland@docker.com> (github: BrianBland)
- handles parsing of WWW-Authenticate response headers
registry/endpoint.go
- EVEN MOAR LOGGING
- Many edits throught to make the coad less dense. Sparse code is more
readable code.
- slit Ping() out to handle different code paths for v1 (unchanged logic)
and v2.
- Updated Endpoint struct type to include an entry for authorization
challenges discovered during ping of a v2 registry.
- If registry endpoint version is unknown, v2 code path is first attempted,
then fallback to v1 upon failure.
registry/service.go
- STILL MOAR LOGGING
- simplified the logic around starting the 'auth' job.
registry/session.go
- updated use of a registry.Endpoint struct field.
registry/token.go
- New File
- Handles getting token from the parameters of a token auth challenge.
- Modified from function written by Brian Bland (see above credit).
registry/types.go
- Removed 'DefaultAPIVersion' in lieu of 'APIVersionUnknown = 0'`

Docker-DCO-1.1-Signed-off-by: Josh Hawn <josh.hawn@docker.com> (github: jlhawn)

Josh Hawn authored on 2014/12/12 10:55:15
Showing 8 changed files
... ...
@@ -11,6 +11,7 @@ import (
11 11
 	"path"
12 12
 	"strings"
13 13
 
14
+	log "github.com/Sirupsen/logrus"
14 15
 	"github.com/docker/docker/utils"
15 16
 )
16 17
 
... ...
@@ -144,8 +145,18 @@ func SaveConfig(configFile *ConfigFile) error {
144 144
 	return nil
145 145
 }
146 146
 
147
-// try to register/login to the registry server
148
-func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, error) {
147
+// Login tries to register/login to the registry server.
148
+func Login(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.HTTPRequestFactory) (string, error) {
149
+	// Separates the v2 registry login logic from the v1 logic.
150
+	if registryEndpoint.Version == APIVersion2 {
151
+		return loginV2(authConfig, registryEndpoint, factory)
152
+	}
153
+
154
+	return loginV1(authConfig, registryEndpoint, factory)
155
+}
156
+
157
+// loginV1 tries to register/login to the v1 registry server.
158
+func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.HTTPRequestFactory) (string, error) {
149 159
 	var (
150 160
 		status  string
151 161
 		reqBody []byte
... ...
@@ -161,6 +172,8 @@ func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, e
161 161
 		serverAddress = authConfig.ServerAddress
162 162
 	)
163 163
 
164
+	log.Debugf("attempting v1 login to registry endpoint %s", registryEndpoint)
165
+
164 166
 	if serverAddress == "" {
165 167
 		return "", fmt.Errorf("Server Error: Server Address not set.")
166 168
 	}
... ...
@@ -253,6 +266,103 @@ func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, e
253 253
 	return status, nil
254 254
 }
255 255
 
256
+// loginV2 tries to login to the v2 registry server. The given registry endpoint has been
257
+// pinged or setup with a list of authorization challenges. Each of these challenges are
258
+// tried until one of them succeeds. Currently supported challenge schemes are:
259
+// 		HTTP Basic Authorization
260
+// 		Token Authorization with a separate token issuing server
261
+// NOTE: the v2 logic does not attempt to create a user account if one doesn't exist. For
262
+// now, users should create their account through other means like directly from a web page
263
+// served by the v2 registry service provider. Whether this will be supported in the future
264
+// is to be determined.
265
+func loginV2(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.HTTPRequestFactory) (string, error) {
266
+	log.Debugf("attempting v2 login to registry endpoint %s", registryEndpoint)
267
+
268
+	client := &http.Client{
269
+		Transport: &http.Transport{
270
+			DisableKeepAlives: true,
271
+			Proxy:             http.ProxyFromEnvironment,
272
+		},
273
+		CheckRedirect: AddRequiredHeadersToRedirectedRequests,
274
+	}
275
+
276
+	var (
277
+		err       error
278
+		allErrors []error
279
+	)
280
+
281
+	for _, challenge := range registryEndpoint.AuthChallenges {
282
+		log.Debugf("trying %q auth challenge with params %s", challenge.Scheme, challenge.Parameters)
283
+
284
+		switch strings.ToLower(challenge.Scheme) {
285
+		case "basic":
286
+			err = tryV2BasicAuthLogin(authConfig, challenge.Parameters, registryEndpoint, client, factory)
287
+		case "bearer":
288
+			err = tryV2TokenAuthLogin(authConfig, challenge.Parameters, registryEndpoint, client, factory)
289
+		default:
290
+			// Unsupported challenge types are explicitly skipped.
291
+			err = fmt.Errorf("unsupported auth scheme: %q", challenge.Scheme)
292
+		}
293
+
294
+		if err == nil {
295
+			return "Login Succeeded", nil
296
+		}
297
+
298
+		log.Debugf("error trying auth challenge %q: %s", challenge.Scheme, err)
299
+
300
+		allErrors = append(allErrors, err)
301
+	}
302
+
303
+	return "", fmt.Errorf("no successful auth challenge for %s - errors: %s", registryEndpoint, allErrors)
304
+}
305
+
306
+func tryV2BasicAuthLogin(authConfig *AuthConfig, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *utils.HTTPRequestFactory) error {
307
+	req, err := factory.NewRequest("GET", registryEndpoint.Path(""), nil)
308
+	if err != nil {
309
+		return err
310
+	}
311
+
312
+	req.SetBasicAuth(authConfig.Username, authConfig.Password)
313
+
314
+	resp, err := client.Do(req)
315
+	if err != nil {
316
+		return err
317
+	}
318
+	defer resp.Body.Close()
319
+
320
+	if resp.StatusCode != http.StatusOK {
321
+		return fmt.Errorf("basic auth attempt to %s realm %q failed with status: %d %s", registryEndpoint, params["realm"], resp.StatusCode, http.StatusText(resp.StatusCode))
322
+	}
323
+
324
+	return nil
325
+}
326
+
327
+func tryV2TokenAuthLogin(authConfig *AuthConfig, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *utils.HTTPRequestFactory) error {
328
+	token, err := getToken(authConfig.Username, authConfig.Password, params, registryEndpoint, client, factory)
329
+	if err != nil {
330
+		return err
331
+	}
332
+
333
+	req, err := factory.NewRequest("GET", registryEndpoint.Path(""), nil)
334
+	if err != nil {
335
+		return err
336
+	}
337
+
338
+	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
339
+
340
+	resp, err := client.Do(req)
341
+	if err != nil {
342
+		return err
343
+	}
344
+	defer resp.Body.Close()
345
+
346
+	if resp.StatusCode != http.StatusOK {
347
+		return fmt.Errorf("token auth attempt to %s realm %q failed with status: %d %s", registryEndpoint, params["realm"], resp.StatusCode, http.StatusText(resp.StatusCode))
348
+	}
349
+
350
+	return nil
351
+}
352
+
256 353
 // this method matches a auth configuration to a server address or a url
257 354
 func (config *ConfigFile) ResolveAuthConfig(index *IndexInfo) AuthConfig {
258 355
 	configKey := index.GetAuthConfigKey()
259 356
new file mode 100644
... ...
@@ -0,0 +1,150 @@
0
+package registry
1
+
2
+import (
3
+	"net/http"
4
+	"strings"
5
+)
6
+
7
+// Octet types from RFC 2616.
8
+type octetType byte
9
+
10
+// AuthorizationChallenge carries information
11
+// from a WWW-Authenticate response header.
12
+type AuthorizationChallenge struct {
13
+	Scheme     string
14
+	Parameters map[string]string
15
+}
16
+
17
+var octetTypes [256]octetType
18
+
19
+const (
20
+	isToken octetType = 1 << iota
21
+	isSpace
22
+)
23
+
24
+func init() {
25
+	// OCTET      = <any 8-bit sequence of data>
26
+	// CHAR       = <any US-ASCII character (octets 0 - 127)>
27
+	// CTL        = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
28
+	// CR         = <US-ASCII CR, carriage return (13)>
29
+	// LF         = <US-ASCII LF, linefeed (10)>
30
+	// SP         = <US-ASCII SP, space (32)>
31
+	// HT         = <US-ASCII HT, horizontal-tab (9)>
32
+	// <">        = <US-ASCII double-quote mark (34)>
33
+	// CRLF       = CR LF
34
+	// LWS        = [CRLF] 1*( SP | HT )
35
+	// TEXT       = <any OCTET except CTLs, but including LWS>
36
+	// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
37
+	//              | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
38
+	// token      = 1*<any CHAR except CTLs or separators>
39
+	// qdtext     = <any TEXT except <">>
40
+
41
+	for c := 0; c < 256; c++ {
42
+		var t octetType
43
+		isCtl := c <= 31 || c == 127
44
+		isChar := 0 <= c && c <= 127
45
+		isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0
46
+		if strings.IndexRune(" \t\r\n", rune(c)) >= 0 {
47
+			t |= isSpace
48
+		}
49
+		if isChar && !isCtl && !isSeparator {
50
+			t |= isToken
51
+		}
52
+		octetTypes[c] = t
53
+	}
54
+}
55
+
56
+func parseAuthHeader(header http.Header) []*AuthorizationChallenge {
57
+	var challenges []*AuthorizationChallenge
58
+	for _, h := range header[http.CanonicalHeaderKey("WWW-Authenticate")] {
59
+		v, p := parseValueAndParams(h)
60
+		if v != "" {
61
+			challenges = append(challenges, &AuthorizationChallenge{Scheme: v, Parameters: p})
62
+		}
63
+	}
64
+	return challenges
65
+}
66
+
67
+func parseValueAndParams(header string) (value string, params map[string]string) {
68
+	params = make(map[string]string)
69
+	value, s := expectToken(header)
70
+	if value == "" {
71
+		return
72
+	}
73
+	value = strings.ToLower(value)
74
+	s = "," + skipSpace(s)
75
+	for strings.HasPrefix(s, ",") {
76
+		var pkey string
77
+		pkey, s = expectToken(skipSpace(s[1:]))
78
+		if pkey == "" {
79
+			return
80
+		}
81
+		if !strings.HasPrefix(s, "=") {
82
+			return
83
+		}
84
+		var pvalue string
85
+		pvalue, s = expectTokenOrQuoted(s[1:])
86
+		if pvalue == "" {
87
+			return
88
+		}
89
+		pkey = strings.ToLower(pkey)
90
+		params[pkey] = pvalue
91
+		s = skipSpace(s)
92
+	}
93
+	return
94
+}
95
+
96
+func skipSpace(s string) (rest string) {
97
+	i := 0
98
+	for ; i < len(s); i++ {
99
+		if octetTypes[s[i]]&isSpace == 0 {
100
+			break
101
+		}
102
+	}
103
+	return s[i:]
104
+}
105
+
106
+func expectToken(s string) (token, rest string) {
107
+	i := 0
108
+	for ; i < len(s); i++ {
109
+		if octetTypes[s[i]]&isToken == 0 {
110
+			break
111
+		}
112
+	}
113
+	return s[:i], s[i:]
114
+}
115
+
116
+func expectTokenOrQuoted(s string) (value string, rest string) {
117
+	if !strings.HasPrefix(s, "\"") {
118
+		return expectToken(s)
119
+	}
120
+	s = s[1:]
121
+	for i := 0; i < len(s); i++ {
122
+		switch s[i] {
123
+		case '"':
124
+			return s[:i], s[i+1:]
125
+		case '\\':
126
+			p := make([]byte, len(s)-1)
127
+			j := copy(p, s[:i])
128
+			escape := true
129
+			for i = i + i; i < len(s); i++ {
130
+				b := s[i]
131
+				switch {
132
+				case escape:
133
+					escape = false
134
+					p[j] = b
135
+					j++
136
+				case b == '\\':
137
+					escape = true
138
+				case b == '"':
139
+					return string(p[:j]), s[i+1:]
140
+				default:
141
+					p[j] = b
142
+					j++
143
+				}
144
+			}
145
+			return "", ""
146
+		}
147
+	}
148
+	return "", ""
149
+}
... ...
@@ -15,28 +15,31 @@ import (
15 15
 // for mocking in unit tests
16 16
 var lookupIP = net.LookupIP
17 17
 
18
-// scans string for api version in the URL path. returns the trimmed hostname, if version found, string and API version.
19
-func scanForAPIVersion(hostname string) (string, APIVersion) {
18
+// scans string for api version in the URL path. returns the trimmed address, if version found, string and API version.
19
+func scanForAPIVersion(address string) (string, APIVersion) {
20 20
 	var (
21 21
 		chunks        []string
22 22
 		apiVersionStr string
23 23
 	)
24
-	if strings.HasSuffix(hostname, "/") {
25
-		chunks = strings.Split(hostname[:len(hostname)-1], "/")
26
-		apiVersionStr = chunks[len(chunks)-1]
27
-	} else {
28
-		chunks = strings.Split(hostname, "/")
29
-		apiVersionStr = chunks[len(chunks)-1]
24
+
25
+	if strings.HasSuffix(address, "/") {
26
+		address = address[:len(address)-1]
30 27
 	}
28
+
29
+	chunks = strings.Split(address, "/")
30
+	apiVersionStr = chunks[len(chunks)-1]
31
+
31 32
 	for k, v := range apiVersions {
32 33
 		if apiVersionStr == v {
33
-			hostname = strings.Join(chunks[:len(chunks)-1], "/")
34
-			return hostname, k
34
+			address = strings.Join(chunks[:len(chunks)-1], "/")
35
+			return address, k
35 36
 		}
36 37
 	}
37
-	return hostname, DefaultAPIVersion
38
+
39
+	return address, APIVersionUnknown
38 40
 }
39 41
 
42
+// NewEndpoint parses the given address to return a registry endpoint.
40 43
 func NewEndpoint(index *IndexInfo) (*Endpoint, error) {
41 44
 	// *TODO: Allow per-registry configuration of endpoints.
42 45
 	endpoint, err := newEndpoint(index.GetAuthConfigKey(), index.Secure)
... ...
@@ -44,81 +47,124 @@ func NewEndpoint(index *IndexInfo) (*Endpoint, error) {
44 44
 		return nil, err
45 45
 	}
46 46
 
47
+	log.Debugf("pinging registry endpoint %s", endpoint)
48
+
47 49
 	// Try HTTPS ping to registry
48 50
 	endpoint.URL.Scheme = "https"
49 51
 	if _, err := endpoint.Ping(); err != nil {
50
-
51
-		//TODO: triggering highland build can be done there without "failing"
52
-
53 52
 		if index.Secure {
54 53
 			// If registry is secure and HTTPS failed, show user the error and tell them about `--insecure-registry`
55 54
 			// in case that's what they need. DO NOT accept unknown CA certificates, and DO NOT fallback to HTTP.
56
-			return nil, fmt.Errorf("Invalid registry endpoint %s: %v. If this private registry supports only HTTP or HTTPS with an unknown CA certificate, please add `--insecure-registry %s` to the daemon's arguments. In the case of HTTPS, if you have access to the registry's CA certificate, no need for the flag; simply place the CA certificate at /etc/docker/certs.d/%s/ca.crt", endpoint, err, endpoint.URL.Host, endpoint.URL.Host)
55
+			return nil, fmt.Errorf("invalid registry endpoint %s: %v. If this private registry supports only HTTP or HTTPS with an unknown CA certificate, please add `--insecure-registry %s` to the daemon's arguments. In the case of HTTPS, if you have access to the registry's CA certificate, no need for the flag; simply place the CA certificate at /etc/docker/certs.d/%s/ca.crt", endpoint, err, endpoint.URL.Host, endpoint.URL.Host)
57 56
 		}
58 57
 
59 58
 		// If registry is insecure and HTTPS failed, fallback to HTTP.
60 59
 		log.Debugf("Error from registry %q marked as insecure: %v. Insecurely falling back to HTTP", endpoint, err)
61 60
 		endpoint.URL.Scheme = "http"
62
-		_, err2 := endpoint.Ping()
63
-		if err2 == nil {
61
+
62
+		var err2 error
63
+		if _, err2 = endpoint.Ping(); err2 == nil {
64 64
 			return endpoint, nil
65 65
 		}
66 66
 
67
-		return nil, fmt.Errorf("Invalid registry endpoint %q. HTTPS attempt: %v. HTTP attempt: %v", endpoint, err, err2)
67
+		return nil, fmt.Errorf("invalid registry endpoint %q. HTTPS attempt: %v. HTTP attempt: %v", endpoint, err, err2)
68 68
 	}
69 69
 
70 70
 	return endpoint, nil
71 71
 }
72
-func newEndpoint(hostname string, secure bool) (*Endpoint, error) {
72
+
73
+func newEndpoint(address string, secure bool) (*Endpoint, error) {
73 74
 	var (
74
-		endpoint        = Endpoint{}
75
-		trimmedHostname string
76
-		err             error
75
+		endpoint       = new(Endpoint)
76
+		trimmedAddress string
77
+		err            error
77 78
 	)
78
-	if !strings.HasPrefix(hostname, "http") {
79
-		hostname = "https://" + hostname
79
+
80
+	if !strings.HasPrefix(address, "http") {
81
+		address = "https://" + address
80 82
 	}
81
-	trimmedHostname, endpoint.Version = scanForAPIVersion(hostname)
82
-	endpoint.URL, err = url.Parse(trimmedHostname)
83
-	if err != nil {
83
+
84
+	trimmedAddress, endpoint.Version = scanForAPIVersion(address)
85
+
86
+	if endpoint.URL, err = url.Parse(trimmedAddress); err != nil {
84 87
 		return nil, err
85 88
 	}
86
-	endpoint.secure = secure
87
-	return &endpoint, nil
89
+	endpoint.IsSecure = secure
90
+	return endpoint, nil
88 91
 }
89 92
 
90 93
 func (repoInfo *RepositoryInfo) GetEndpoint() (*Endpoint, error) {
91 94
 	return NewEndpoint(repoInfo.Index)
92 95
 }
93 96
 
97
+// Endpoint stores basic information about a registry endpoint.
94 98
 type Endpoint struct {
95
-	URL     *url.URL
96
-	Version APIVersion
97
-	secure  bool
99
+	URL            *url.URL
100
+	Version        APIVersion
101
+	IsSecure       bool
102
+	AuthChallenges []*AuthorizationChallenge
98 103
 }
99 104
 
100 105
 // Get the formated URL for the root of this registry Endpoint
101
-func (e Endpoint) String() string {
102
-	return fmt.Sprintf("%s/v%d/", e.URL.String(), e.Version)
106
+func (e *Endpoint) String() string {
107
+	return fmt.Sprintf("%s/v%d/", e.URL, e.Version)
108
+}
109
+
110
+// VersionString returns a formatted string of this
111
+// endpoint address using the given API Version.
112
+func (e *Endpoint) VersionString(version APIVersion) string {
113
+	return fmt.Sprintf("%s/v%d/", e.URL, version)
103 114
 }
104 115
 
105
-func (e Endpoint) VersionString(version APIVersion) string {
106
-	return fmt.Sprintf("%s/v%d/", e.URL.String(), version)
116
+// Path returns a formatted string for the URL
117
+// of this endpoint with the given path appended.
118
+func (e *Endpoint) Path(path string) string {
119
+	return fmt.Sprintf("%s/v%d/%s", e.URL, e.Version, path)
107 120
 }
108 121
 
109
-func (e Endpoint) Ping() (RegistryInfo, error) {
122
+func (e *Endpoint) Ping() (RegistryInfo, error) {
123
+	// The ping logic to use is determined by the registry endpoint version.
124
+	switch e.Version {
125
+	case APIVersion1:
126
+		return e.pingV1()
127
+	case APIVersion2:
128
+		return e.pingV2()
129
+	}
130
+
131
+	// APIVersionUnknown
132
+	// We should try v2 first...
133
+	e.Version = APIVersion2
134
+	regInfo, errV2 := e.pingV2()
135
+	if errV2 == nil {
136
+		return regInfo, nil
137
+	}
138
+
139
+	// ... then fallback to v1.
140
+	e.Version = APIVersion1
141
+	regInfo, errV1 := e.pingV1()
142
+	if errV1 == nil {
143
+		return regInfo, nil
144
+	}
145
+
146
+	e.Version = APIVersionUnknown
147
+	return RegistryInfo{}, fmt.Errorf("unable to ping registry endpoint %s\nv2 ping attempt failed with error: %s\n v1 ping attempt failed with error: %s", e, errV2, errV1)
148
+}
149
+
150
+func (e *Endpoint) pingV1() (RegistryInfo, error) {
151
+	log.Debugf("attempting v1 ping for registry endpoint %s", e)
152
+
110 153
 	if e.String() == IndexServerAddress() {
111
-		// Skip the check, we now this one is valid
154
+		// Skip the check, we know this one is valid
112 155
 		// (and we never want to fallback to http in case of error)
113 156
 		return RegistryInfo{Standalone: false}, nil
114 157
 	}
115 158
 
116
-	req, err := http.NewRequest("GET", e.String()+"_ping", nil)
159
+	req, err := http.NewRequest("GET", e.Path("_ping"), nil)
117 160
 	if err != nil {
118 161
 		return RegistryInfo{Standalone: false}, err
119 162
 	}
120 163
 
121
-	resp, _, err := doRequest(req, nil, ConnectTimeout, e.secure)
164
+	resp, _, err := doRequest(req, nil, ConnectTimeout, e.IsSecure)
122 165
 	if err != nil {
123 166
 		return RegistryInfo{Standalone: false}, err
124 167
 	}
... ...
@@ -127,7 +173,7 @@ func (e Endpoint) Ping() (RegistryInfo, error) {
127 127
 
128 128
 	jsonString, err := ioutil.ReadAll(resp.Body)
129 129
 	if err != nil {
130
-		return RegistryInfo{Standalone: false}, fmt.Errorf("Error while reading the http response: %s", err)
130
+		return RegistryInfo{Standalone: false}, fmt.Errorf("error while reading the http response: %s", err)
131 131
 	}
132 132
 
133 133
 	// If the header is absent, we assume true for compatibility with earlier
... ...
@@ -157,3 +203,33 @@ func (e Endpoint) Ping() (RegistryInfo, error) {
157 157
 	log.Debugf("RegistryInfo.Standalone: %t", info.Standalone)
158 158
 	return info, nil
159 159
 }
160
+
161
+func (e *Endpoint) pingV2() (RegistryInfo, error) {
162
+	log.Debugf("attempting v2 ping for registry endpoint %s", e)
163
+
164
+	req, err := http.NewRequest("GET", e.Path(""), nil)
165
+	if err != nil {
166
+		return RegistryInfo{}, err
167
+	}
168
+
169
+	resp, _, err := doRequest(req, nil, ConnectTimeout, e.IsSecure)
170
+	if err != nil {
171
+		return RegistryInfo{}, err
172
+	}
173
+	defer resp.Body.Close()
174
+
175
+	if resp.StatusCode == http.StatusOK {
176
+		// It would seem that no authentication/authorization is required.
177
+		// So we don't need to parse/add any authorization schemes.
178
+		return RegistryInfo{Standalone: true}, nil
179
+	}
180
+
181
+	if resp.StatusCode == http.StatusUnauthorized {
182
+		// Parse the WWW-Authenticate Header and store the challenges
183
+		// on this endpoint object.
184
+		e.AuthChallenges = parseAuthHeader(resp.Header)
185
+		return RegistryInfo{}, nil
186
+	}
187
+
188
+	return RegistryInfo{}, fmt.Errorf("v2 registry endpoint returned status %d: %q", resp.StatusCode, http.StatusText(resp.StatusCode))
189
+}
... ...
@@ -8,8 +8,10 @@ func TestEndpointParse(t *testing.T) {
8 8
 		expected string
9 9
 	}{
10 10
 		{IndexServerAddress(), IndexServerAddress()},
11
-		{"http://0.0.0.0:5000", "http://0.0.0.0:5000/v1/"},
12
-		{"0.0.0.0:5000", "https://0.0.0.0:5000/v1/"},
11
+		{"http://0.0.0.0:5000/v1/", "http://0.0.0.0:5000/v1/"},
12
+		{"http://0.0.0.0:5000/v2/", "http://0.0.0.0:5000/v2/"},
13
+		{"http://0.0.0.0:5000", "http://0.0.0.0:5000/v0/"},
14
+		{"0.0.0.0:5000", "https://0.0.0.0:5000/v0/"},
13 15
 	}
14 16
 	for _, td := range testData {
15 17
 		e, err := newEndpoint(td.str, false)
... ...
@@ -1,6 +1,7 @@
1 1
 package registry
2 2
 
3 3
 import (
4
+	log "github.com/Sirupsen/logrus"
4 5
 	"github.com/docker/docker/engine"
5 6
 )
6 7
 
... ...
@@ -38,28 +39,39 @@ func (s *Service) Install(eng *engine.Engine) error {
38 38
 // and returns OK if authentication was sucessful.
39 39
 // It can be used to verify the validity of a client's credentials.
40 40
 func (s *Service) Auth(job *engine.Job) engine.Status {
41
-	var authConfig = new(AuthConfig)
41
+	var (
42
+		authConfig = new(AuthConfig)
43
+		endpoint   *Endpoint
44
+		index      *IndexInfo
45
+		status     string
46
+		err        error
47
+	)
42 48
 
43 49
 	job.GetenvJson("authConfig", authConfig)
44 50
 
45
-	if authConfig.ServerAddress != "" {
46
-		index, err := ResolveIndexInfo(job, authConfig.ServerAddress)
47
-		if err != nil {
48
-			return job.Error(err)
49
-		}
50
-		if !index.Official {
51
-			endpoint, err := NewEndpoint(index)
52
-			if err != nil {
53
-				return job.Error(err)
54
-			}
55
-			authConfig.ServerAddress = endpoint.String()
56
-		}
57
-	}
58
-
59
-	status, err := Login(authConfig, HTTPRequestFactory(nil))
60
-	if err != nil {
51
+	addr := authConfig.ServerAddress
52
+	if addr == "" {
53
+		// Use the official registry address if not specified.
54
+		addr = IndexServerAddress()
55
+	}
56
+
57
+	if index, err = ResolveIndexInfo(job, addr); err != nil {
61 58
 		return job.Error(err)
62 59
 	}
60
+
61
+	if endpoint, err = NewEndpoint(index); err != nil {
62
+		log.Errorf("unable to get new registry endpoint: %s", err)
63
+		return job.Error(err)
64
+	}
65
+
66
+	authConfig.ServerAddress = endpoint.String()
67
+
68
+	if status, err = Login(authConfig, endpoint, HTTPRequestFactory(nil)); err != nil {
69
+		log.Errorf("unable to login against registry endpoint %s: %s", endpoint, err)
70
+		return job.Error(err)
71
+	}
72
+
73
+	log.Infof("successful registry login for endpoint %s: %s", endpoint, status)
63 74
 	job.Printf("%s\n", status)
64 75
 
65 76
 	return engine.StatusOK
... ...
@@ -65,7 +65,7 @@ func NewSession(authConfig *AuthConfig, factory *utils.HTTPRequestFactory, endpo
65 65
 }
66 66
 
67 67
 func (r *Session) doRequest(req *http.Request) (*http.Response, *http.Client, error) {
68
-	return doRequest(req, r.jar, r.timeout, r.indexEndpoint.secure)
68
+	return doRequest(req, r.jar, r.timeout, r.indexEndpoint.IsSecure)
69 69
 }
70 70
 
71 71
 // Retrieve the history of a given image from the Registry.
72 72
new file mode 100644
... ...
@@ -0,0 +1,70 @@
0
+package registry
1
+
2
+import (
3
+	"errors"
4
+	"fmt"
5
+	"net/http"
6
+	"net/url"
7
+	"strings"
8
+
9
+	"github.com/docker/docker/utils"
10
+)
11
+
12
+func getToken(username, password string, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *utils.HTTPRequestFactory) (token string, err error) {
13
+	realm, ok := params["realm"]
14
+	if !ok {
15
+		return "", errors.New("no realm specified for token auth challenge")
16
+	}
17
+
18
+	realmURL, err := url.Parse(realm)
19
+	if err != nil {
20
+		return "", fmt.Errorf("invalid token auth challenge realm: %s", err)
21
+	}
22
+
23
+	if realmURL.Scheme == "" {
24
+		if registryEndpoint.IsSecure {
25
+			realmURL.Scheme = "https"
26
+		} else {
27
+			realmURL.Scheme = "http"
28
+		}
29
+	}
30
+
31
+	req, err := factory.NewRequest("GET", realmURL.String(), nil)
32
+	if err != nil {
33
+		return "", err
34
+	}
35
+
36
+	reqParams := req.URL.Query()
37
+	service := params["service"]
38
+	scope := params["scope"]
39
+
40
+	if service != "" {
41
+		reqParams.Add("service", service)
42
+	}
43
+
44
+	for _, scopeField := range strings.Fields(scope) {
45
+		reqParams.Add("scope", scopeField)
46
+	}
47
+
48
+	reqParams.Add("account", username)
49
+
50
+	req.URL.RawQuery = reqParams.Encode()
51
+	req.SetBasicAuth(username, password)
52
+
53
+	resp, err := client.Do(req)
54
+	if err != nil {
55
+		return "", err
56
+	}
57
+	defer resp.Body.Close()
58
+
59
+	if !(resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNoContent) {
60
+		return "", fmt.Errorf("token auth attempt for registry %s: %s request failed with status: %d %s", registryEndpoint, req.URL, resp.StatusCode, http.StatusText(resp.StatusCode))
61
+	}
62
+
63
+	token = resp.Header.Get("X-Auth-Token")
64
+	if token == "" {
65
+		return "", errors.New("token server did not include a token in the response header")
66
+	}
67
+
68
+	return token, nil
69
+}
... ...
@@ -55,14 +55,15 @@ func (av APIVersion) String() string {
55 55
 	return apiVersions[av]
56 56
 }
57 57
 
58
-var DefaultAPIVersion APIVersion = APIVersion1
59 58
 var apiVersions = map[APIVersion]string{
60 59
 	1: "v1",
61 60
 	2: "v2",
62 61
 }
63 62
 
63
+// API Version identifiers.
64 64
 const (
65
-	APIVersion1 = iota + 1
65
+	APIVersionUnknown = iota
66
+	APIVersion1
66 67
 	APIVersion2
67 68
 )
68 69