@@ -24,6 +24,7 @@ import (
24
24
"net/http"
25
25
"strings"
26
26
"sync"
27
+ "time"
27
28
28
29
"github.com/containerd/containerd/v2/core/remotes/docker/auth"
29
30
remoteerrors "github.com/containerd/containerd/v2/core/remotes/errors"
@@ -205,9 +206,10 @@ func (a *dockerAuthorizer) AddResponses(ctx context.Context, responses []*http.R
205
206
// authResult is used to control limit rate.
206
207
type authResult struct {
207
208
sync.WaitGroup
208
- token string
209
- refreshToken string
210
- err error
209
+ token string
210
+ refreshToken string
211
+ expirationTime * time.Time
212
+ err error
211
213
}
212
214
213
215
// authHandler is used to handle auth request per registry server.
@@ -270,8 +272,12 @@ func (ah *authHandler) doBearerAuth(ctx context.Context) (token, refreshToken st
270
272
// Docs: https://docs.docker.com/registry/spec/auth/scope
271
273
scoped := strings .Join (to .Scopes , " " )
272
274
275
+ // Keep track of the expiration time of cached bearer tokens so they can be
276
+ // refreshed when they expire without a server roundtrip.
277
+ var expirationTime * time.Time
278
+
273
279
ah .Lock ()
274
- if r , exist := ah .scopedTokens [scoped ]; exist {
280
+ if r , exist := ah .scopedTokens [scoped ]; exist && ( r . expirationTime == nil || r . expirationTime . After ( time . Now ())) {
275
281
ah .Unlock ()
276
282
r .Wait ()
277
283
return r .token , r .refreshToken , r .err
@@ -285,7 +291,7 @@ func (ah *authHandler) doBearerAuth(ctx context.Context) (token, refreshToken st
285
291
286
292
defer func () {
287
293
token = fmt .Sprintf ("Bearer %s" , token )
288
- r .token , r .refreshToken , r .err = token , refreshToken , err
294
+ r .token , r .refreshToken , r .err , r . expirationTime = token , refreshToken , err , expirationTime
289
295
r .Done ()
290
296
}()
291
297
@@ -311,6 +317,7 @@ func (ah *authHandler) doBearerAuth(ctx context.Context) (token, refreshToken st
311
317
if err != nil {
312
318
return "" , "" , err
313
319
}
320
+ expirationTime = getExpirationTime (resp .ExpiresInSeconds )
314
321
return resp .Token , resp .RefreshToken , nil
315
322
}
316
323
log .G (ctx ).WithFields (log.Fields {
@@ -320,16 +327,26 @@ func (ah *authHandler) doBearerAuth(ctx context.Context) (token, refreshToken st
320
327
}
321
328
return "" , "" , err
322
329
}
330
+ expirationTime = getExpirationTime (resp .ExpiresInSeconds )
323
331
return resp .AccessToken , resp .RefreshToken , nil
324
332
}
325
333
// do request anonymously
326
334
resp , err := auth .FetchToken (ctx , ah .client , ah .header , to )
327
335
if err != nil {
328
336
return "" , "" , fmt .Errorf ("failed to fetch anonymous token: %w" , err )
329
337
}
338
+ expirationTime = getExpirationTime (resp .ExpiresInSeconds )
330
339
return resp .Token , resp .RefreshToken , nil
331
340
}
332
341
342
+ func getExpirationTime (expiresInSeconds int ) * time.Time {
343
+ if expiresInSeconds <= 0 {
344
+ return nil
345
+ }
346
+ expirationTime := time .Now ().Add (time .Duration (expiresInSeconds ) * time .Second )
347
+ return & expirationTime
348
+ }
349
+
333
350
func invalidAuthorization (ctx context.Context , c auth.Challenge , responses []* http.Response ) (retry bool , _ error ) {
334
351
errStr := c .Parameters ["error" ]
335
352
if errStr == "" {
0 commit comments