@@ -45,14 +45,14 @@ var ErrV1NotSupported = errors.New("can't talk to a V1 docker registry")
45
45
46
46
// dockerClient is configuration for dealing with a single Docker registry.
47
47
type dockerClient struct {
48
- ctx * types.SystemContext
49
- registry string
50
- username string
51
- password string
52
- wwwAuthenticate string // Cache of a value set by ping() if scheme is not empty
53
- scheme string // Cache of a value returned by a successful ping() if not empty
54
- client * http. Client
55
- signatureBase signatureStorageBase
48
+ ctx * types.SystemContext
49
+ registry string
50
+ username string
51
+ password string
52
+ scheme string // Cache of a value returned by a successful ping() if not empty
53
+ client * http. Client
54
+ signatureBase signatureStorageBase
55
+ challenges [] challenge
56
56
}
57
57
58
58
// this is cloned from docker/go-connections because upstream docker has changed
@@ -187,27 +187,31 @@ func newDockerClient(ctx *types.SystemContext, ref dockerReference, write bool)
187
187
}, nil
188
188
}
189
189
190
+ type requestOptions struct {
191
+ remoteName string
192
+ actions string
193
+ }
194
+
190
195
// makeRequest creates and executes a http.Request with the specified parameters, adding authentication and TLS options for the Docker client.
191
196
// url is NOT an absolute URL, but a path relative to the /v2/ top-level API path. The host name and schema is taken from the client or autodetected.
192
- func (c * dockerClient ) makeRequest (method , url string , headers map [string ][]string , stream io.Reader ) (* http.Response , error ) {
197
+ func (c * dockerClient ) makeRequest (method , url string , headers map [string ][]string , stream io.Reader , opts requestOptions ) (* http.Response , error ) {
193
198
if c .scheme == "" {
194
199
pr , err := c .ping ()
195
200
if err != nil {
196
201
return nil , err
197
202
}
198
- c .wwwAuthenticate = pr .WWWAuthenticate
199
203
c .scheme = pr .scheme
200
204
}
201
205
202
206
url = fmt .Sprintf (baseURL , c .scheme , c .registry ) + url
203
- return c .makeRequestToResolvedURL (method , url , headers , stream , - 1 , true )
207
+ return c .makeRequestToResolvedURL (method , url , headers , stream , - 1 , true , opts )
204
208
}
205
209
206
210
// makeRequestToResolvedURL creates and executes a http.Request with the specified parameters, adding authentication and TLS options for the Docker client.
207
211
// streamLen, if not -1, specifies the length of the data expected on stream.
208
212
// makeRequest should generally be preferred.
209
213
// TODO(runcom): too many arguments here, use a struct
210
- func (c * dockerClient ) makeRequestToResolvedURL (method , url string , headers map [string ][]string , stream io.Reader , streamLen int64 , sendAuth bool ) (* http.Response , error ) {
214
+ func (c * dockerClient ) makeRequestToResolvedURL (method , url string , headers map [string ][]string , stream io.Reader , streamLen int64 , sendAuth bool , opts requestOptions ) (* http.Response , error ) {
211
215
req , err := http .NewRequest (method , url , stream )
212
216
if err != nil {
213
217
return nil , err
@@ -224,8 +228,8 @@ func (c *dockerClient) makeRequestToResolvedURL(method, url string, headers map[
224
228
if c .ctx != nil && c .ctx .DockerRegistryUserAgent != "" {
225
229
req .Header .Add ("User-Agent" , c .ctx .DockerRegistryUserAgent )
226
230
}
227
- if c . wwwAuthenticate != "" && sendAuth {
228
- if err := c .setupRequestAuth (req ); err != nil {
231
+ if sendAuth {
232
+ if err := c .setupRequestAuth (req , opts . remoteName , opts . actions ); err != nil {
229
233
return nil , err
230
234
}
231
235
}
@@ -237,87 +241,31 @@ func (c *dockerClient) makeRequestToResolvedURL(method, url string, headers map[
237
241
return res , nil
238
242
}
239
243
240
- func (c * dockerClient ) setupRequestAuth (req * http.Request ) error {
241
- tokens := strings .SplitN (strings .TrimSpace (c .wwwAuthenticate ), " " , 2 )
242
- if len (tokens ) != 2 {
243
- return errors .Errorf ("expected 2 tokens in WWW-Authenticate: %d, %s" , len (tokens ), c .wwwAuthenticate )
244
+ func (c * dockerClient ) setupRequestAuth (req * http.Request , remoteName , actions string ) error {
245
+ if len (c .challenges ) == 0 {
246
+ return nil
244
247
}
245
- switch tokens [0 ] {
246
- case "Basic" :
248
+ // assume just one...
249
+ challenge := c .challenges [0 ]
250
+ switch challenge .Scheme {
251
+ case "basic" :
247
252
req .SetBasicAuth (c .username , c .password )
248
253
return nil
249
- case "Bearer" :
250
- // FIXME? This gets a new token for every API request;
251
- // we may be easily able to reuse a previous token, e.g.
252
- // for OpenShift the token only identifies the user and does not vary
253
- // across operations. Should we just try the request first, and
254
- // only get a new token on failure?
255
- // OTOH what to do with the single-use body stream in that case?
256
-
257
- // Try performing the request, expecting it to fail.
258
- testReq := * req
259
- // Do not use the body stream, or we couldn't reuse it for the "real" call later.
260
- testReq .Body = nil
261
- testReq .ContentLength = 0
262
- res , err := c .client .Do (& testReq )
263
- if err != nil {
264
- return err
265
- }
266
- chs := parseAuthHeader (res .Header )
267
- // We could end up in this "if" statement if the /v2/ call (during ping)
268
- // returned 401 with a valid WWW-Authenticate=Bearer header.
269
- // That doesn't **always** mean, however, that the specific API request
270
- // (different from /v2/) actually needs to be authorized.
271
- // One example of this _weird_ scenario happens with GCR.io docker
272
- // registries.
273
- if res .StatusCode != http .StatusUnauthorized || chs == nil || len (chs ) == 0 {
274
- // With gcr.io, the /v2/ call returns a 401 with a valid WWW-Authenticate=Bearer
275
- // header but the repository could be _public_ (no authorization is needed).
276
- // Hence, the registry response contains no challenges and the status
277
- // code is not 401.
278
- // We just skip this case as it's not standard on docker/distribution
279
- // registries (https://github.com/docker/distribution/blob/master/docs/spec/api.md#api-version-check)
280
- if res .StatusCode != http .StatusUnauthorized {
281
- return nil
282
- }
283
- // gcr.io private repositories pull instead requires us to send user:pass pair in
284
- // order to retrieve a token and setup the correct Bearer token.
285
- // try again one last time with Basic Auth
286
- testReq2 := * req
287
- // Do not use the body stream, or we couldn't reuse it for the "real" call later.
288
- testReq2 .Body = nil
289
- testReq2 .ContentLength = 0
290
- testReq2 .SetBasicAuth (c .username , c .password )
291
- res , err := c .client .Do (& testReq2 )
292
- if err != nil {
293
- return err
294
- }
295
- chs = parseAuthHeader (res .Header )
296
- if res .StatusCode != http .StatusUnauthorized || chs == nil || len (chs ) == 0 {
297
- // no need for bearer? wtf?
298
- return nil
299
- }
300
- }
301
- // Arbitrarily use the first challenge, there is no reason to expect more than one.
302
- challenge := chs [0 ]
303
- if challenge .Scheme != "bearer" { // Another artifact of trying to handle WWW-Authenticate before it actually happens.
304
- return errors .Errorf ("Unimplemented: WWW-Authenticate Bearer replaced by %#v" , challenge .Scheme )
305
- }
254
+ case "bearer" :
306
255
realm , ok := challenge .Parameters ["realm" ]
307
256
if ! ok {
308
257
return errors .Errorf ("missing realm in bearer auth challenge" )
309
258
}
310
259
service , _ := challenge .Parameters ["service" ] // Will be "" if not present
311
- scope , _ := challenge . Parameters [ "scope" ] // Will be "" if not present
260
+ scope := fmt . Sprintf ( "repository:%s:%s" , remoteName , actions )
312
261
token , err := c .getBearerToken (realm , service , scope )
313
262
if err != nil {
314
263
return err
315
264
}
316
265
req .Header .Set ("Authorization" , fmt .Sprintf ("Bearer %s" , token ))
317
266
return nil
318
267
}
319
- return errors .Errorf ("no handler for %s authentication" , tokens [0 ])
320
- // support docker bearer with authconfig's Auth string? see docker2aci
268
+ return errors .Errorf ("no handler for %s authentication" , challenge .Scheme )
321
269
}
322
270
323
271
func (c * dockerClient ) getBearerToken (realm , service , scope string ) (string , error ) {
@@ -428,15 +376,14 @@ func getAuth(ctx *types.SystemContext, registry string) (string, string, error)
428
376
}
429
377
430
378
type pingResponse struct {
431
- WWWAuthenticate string
432
- APIVersion string
433
- scheme string
379
+ APIVersion string
380
+ scheme string
434
381
}
435
382
436
383
func (c * dockerClient ) ping () (* pingResponse , error ) {
437
384
ping := func (scheme string ) (* pingResponse , error ) {
438
385
url := fmt .Sprintf (baseURL , scheme , c .registry )
439
- resp , err := c .makeRequestToResolvedURL ("GET" , url , nil , nil , - 1 , true )
386
+ resp , err := c .makeRequestToResolvedURL ("GET" , url , nil , nil , - 1 , true , requestOptions {} )
440
387
logrus .Debugf ("Ping %s err %#v" , url , err )
441
388
if err != nil {
442
389
return nil , err
@@ -446,8 +393,9 @@ func (c *dockerClient) ping() (*pingResponse, error) {
446
393
if resp .StatusCode != http .StatusOK && resp .StatusCode != http .StatusUnauthorized {
447
394
return nil , errors .Errorf ("error pinging repository, response code %d" , resp .StatusCode )
448
395
}
396
+ c .challenges = parseAuthHeader (resp .Header )
397
+
449
398
pr := & pingResponse {}
450
- pr .WWWAuthenticate = resp .Header .Get ("WWW-Authenticate" )
451
399
pr .APIVersion = resp .Header .Get ("Docker-Distribution-Api-Version" )
452
400
pr .scheme = scheme
453
401
return pr , nil
@@ -464,7 +412,7 @@ func (c *dockerClient) ping() (*pingResponse, error) {
464
412
// best effort to understand if we're talking to a V1 registry
465
413
pingV1 := func (scheme string ) bool {
466
414
url := fmt .Sprintf (baseURLV1 , scheme , c .registry )
467
- resp , err := c .makeRequestToResolvedURL ("GET" , url , nil , nil , - 1 , true )
415
+ resp , err := c .makeRequestToResolvedURL ("GET" , url , nil , nil , - 1 , true , requestOptions {} )
468
416
logrus .Debugf ("Ping %s err %#v" , url , err )
469
417
if err != nil {
470
418
return false
0 commit comments