Skip to content

Commit af6a90b

Browse files
remotes/docker/authorizer.go: invalidate auth tokens when they expire.
Signed-off-by: Iain Macdonald <[email protected]>
1 parent be9336f commit af6a90b

File tree

2 files changed

+32
-15
lines changed

2 files changed

+32
-15
lines changed

core/remotes/docker/auth/fetch.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,11 @@ type TokenOptions struct {
8686

8787
// OAuthTokenResponse is response from fetching token with a OAuth POST request
8888
type OAuthTokenResponse struct {
89-
AccessToken string `json:"access_token"`
90-
RefreshToken string `json:"refresh_token"`
91-
ExpiresIn int `json:"expires_in"`
92-
IssuedAt time.Time `json:"issued_at"`
93-
Scope string `json:"scope"`
89+
AccessToken string `json:"access_token"`
90+
RefreshToken string `json:"refresh_token"`
91+
ExpiresInSeconds int `json:"expires_in"`
92+
IssuedAt time.Time `json:"issued_at"`
93+
Scope string `json:"scope"`
9494
}
9595

9696
// FetchTokenWithOAuth fetches a token using a POST request
@@ -152,11 +152,11 @@ func FetchTokenWithOAuth(ctx context.Context, client *http.Client, headers http.
152152

153153
// FetchTokenResponse is response from fetching token with GET request
154154
type FetchTokenResponse struct {
155-
Token string `json:"token"`
156-
AccessToken string `json:"access_token"`
157-
ExpiresIn int `json:"expires_in"`
158-
IssuedAt time.Time `json:"issued_at"`
159-
RefreshToken string `json:"refresh_token"`
155+
Token string `json:"token"`
156+
AccessToken string `json:"access_token"`
157+
ExpiresInSeconds int `json:"expires_in"`
158+
IssuedAt time.Time `json:"issued_at"`
159+
RefreshToken string `json:"refresh_token"`
160160
}
161161

162162
// FetchToken fetches a token using a GET request

core/remotes/docker/authorizer.go

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"net/http"
2525
"strings"
2626
"sync"
27+
"time"
2728

2829
"github.com/containerd/containerd/v2/core/remotes/docker/auth"
2930
remoteerrors "github.com/containerd/containerd/v2/core/remotes/errors"
@@ -205,9 +206,10 @@ func (a *dockerAuthorizer) AddResponses(ctx context.Context, responses []*http.R
205206
// authResult is used to control limit rate.
206207
type authResult struct {
207208
sync.WaitGroup
208-
token string
209-
refreshToken string
210-
err error
209+
token string
210+
refreshToken string
211+
expirationTime *time.Time
212+
err error
211213
}
212214

213215
// authHandler is used to handle auth request per registry server.
@@ -270,8 +272,12 @@ func (ah *authHandler) doBearerAuth(ctx context.Context) (token, refreshToken st
270272
// Docs: https://docs.docker.com/registry/spec/auth/scope
271273
scoped := strings.Join(to.Scopes, " ")
272274

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+
273279
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())) {
275281
ah.Unlock()
276282
r.Wait()
277283
return r.token, r.refreshToken, r.err
@@ -285,7 +291,7 @@ func (ah *authHandler) doBearerAuth(ctx context.Context) (token, refreshToken st
285291

286292
defer func() {
287293
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
289295
r.Done()
290296
}()
291297

@@ -311,6 +317,7 @@ func (ah *authHandler) doBearerAuth(ctx context.Context) (token, refreshToken st
311317
if err != nil {
312318
return "", "", err
313319
}
320+
expirationTime = getExpirationTime(resp.ExpiresInSeconds)
314321
return resp.Token, resp.RefreshToken, nil
315322
}
316323
log.G(ctx).WithFields(log.Fields{
@@ -320,16 +327,26 @@ func (ah *authHandler) doBearerAuth(ctx context.Context) (token, refreshToken st
320327
}
321328
return "", "", err
322329
}
330+
expirationTime = getExpirationTime(resp.ExpiresInSeconds)
323331
return resp.AccessToken, resp.RefreshToken, nil
324332
}
325333
// do request anonymously
326334
resp, err := auth.FetchToken(ctx, ah.client, ah.header, to)
327335
if err != nil {
328336
return "", "", fmt.Errorf("failed to fetch anonymous token: %w", err)
329337
}
338+
expirationTime = getExpirationTime(resp.ExpiresInSeconds)
330339
return resp.Token, resp.RefreshToken, nil
331340
}
332341

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+
333350
func invalidAuthorization(ctx context.Context, c auth.Challenge, responses []*http.Response) (retry bool, _ error) {
334351
errStr := c.Parameters["error"]
335352
if errStr == "" {

0 commit comments

Comments
 (0)