Skip to content

Commit 061aeaf

Browse files
author
Adam Talbot
committed
When using WaitAuthorization or WaitOrder is not practical due to its blocking,
for example in Kubernetes controllers, the GetAuthorization can be used. This change adds the RetryAfter field to the Authorization, Order and Challenge objects returned by GetAuthorization, GetOrder and GetChallenge so it can be used by these implementations that use their own polling mechanism. The new RetryAfter field is populated by the "Retry-After" header in the HTTP response. Fixes golang/go#74454 Signed-off-by: Adam Talbot <[email protected]>
1 parent 459a9db commit 061aeaf

File tree

3 files changed

+39
-12
lines changed

3 files changed

+39
-12
lines changed

acme/acme.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ func (c *Client) authorize(ctx context.Context, typ, val string) (*Authorization
381381
if v.Status != StatusPending && v.Status != StatusValid {
382382
return nil, fmt.Errorf("acme: unexpected status: %s", v.Status)
383383
}
384-
return v.authorization(res.Header.Get("Location")), nil
384+
return v.authorization(res.Header.Get("Location"), 0), nil
385385
}
386386

387387
// GetAuthorization retrieves an authorization identified by the given URL.
@@ -402,7 +402,8 @@ func (c *Client) GetAuthorization(ctx context.Context, url string) (*Authorizati
402402
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
403403
return nil, fmt.Errorf("acme: invalid response: %v", err)
404404
}
405-
return v.authorization(url), nil
405+
d := retryAfter(res.Header.Get("Retry-After"))
406+
return v.authorization(url, d), nil
406407
}
407408

408409
// RevokeAuthorization relinquishes an existing authorization identified
@@ -460,7 +461,7 @@ func (c *Client) WaitAuthorization(ctx context.Context, url string) (*Authorizat
460461
case err != nil:
461462
// Skip and retry.
462463
case raw.Status == StatusValid:
463-
return raw.authorization(url), nil
464+
return raw.authorization(url, 0), nil
464465
case raw.Status == StatusInvalid:
465466
return nil, raw.error(url)
466467
}
@@ -505,7 +506,8 @@ func (c *Client) GetChallenge(ctx context.Context, url string) (*Challenge, erro
505506
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
506507
return nil, fmt.Errorf("acme: invalid response: %v", err)
507508
}
508-
return v.challenge(), nil
509+
d := retryAfter(res.Header.Get("Retry-After"))
510+
return v.challenge(d), nil
509511
}
510512

511513
// Accept informs the server that the client accepts one of its challenges
@@ -534,7 +536,7 @@ func (c *Client) Accept(ctx context.Context, chal *Challenge) (*Challenge, error
534536
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
535537
return nil, fmt.Errorf("acme: invalid response: %v", err)
536538
}
537-
return v.challenge(), nil
539+
return v.challenge(0), nil
538540
}
539541

540542
// DNS01ChallengeRecord returns a DNS record value for a dns-01 challenge response.

acme/rfc8555.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ func responseOrder(res *http.Response) (*Order, error) {
318318
AuthzURLs: v.Authorizations,
319319
FinalizeURL: v.Finalize,
320320
CertURL: v.Certificate,
321+
RetryAfter: retryAfter(res.Header.Get("Retry-After")),
321322
}
322323
for _, id := range v.Identifiers {
323324
o.Identifiers = append(o.Identifiers, AuthzID{Type: id.Type, Value: id.Value})

acme/types.go

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,13 @@ type Order struct {
366366

367367
// The error that occurred while processing the order as received from a CA, if any.
368368
Error *Error
369+
370+
// RetryAfter specifies how long the client should wait before polling the order again,
371+
// based on the Retry-After header provided by the server while the order is in the
372+
// StatusProcessing state.
373+
//
374+
// See RFC 8555 Section 7.4.
375+
RetryAfter time.Duration
369376
}
370377

371378
// OrderOption allows customizing Client.AuthorizeOrder call.
@@ -426,6 +433,14 @@ type Authorization struct {
426433
//
427434
// This field is unused in RFC 8555.
428435
Combinations [][]int
436+
437+
// RetryAfter specifies how long the client should wait before polling the
438+
// authorization resource again, if indicated by the server.
439+
// This corresponds to the optional Retry-After HTTP header included in a
440+
// 200 (OK) response when the authorization is still StatusPending.
441+
//
442+
// See RFC 8555 Section 7.5.1.
443+
RetryAfter time.Duration
429444
}
430445

431446
// AuthzID is an identifier that an account is authorized to represent.
@@ -471,7 +486,7 @@ type wireAuthz struct {
471486
Error *wireError
472487
}
473488

474-
func (z *wireAuthz) authorization(uri string) *Authorization {
489+
func (z *wireAuthz) authorization(uri string, retryAfter time.Duration) *Authorization {
475490
a := &Authorization{
476491
URI: uri,
477492
Status: z.Status,
@@ -480,9 +495,10 @@ func (z *wireAuthz) authorization(uri string) *Authorization {
480495
Wildcard: z.Wildcard,
481496
Challenges: make([]*Challenge, len(z.Challenges)),
482497
Combinations: z.Combinations, // shallow copy
498+
RetryAfter: retryAfter,
483499
}
484500
for i, v := range z.Challenges {
485-
a.Challenges[i] = v.challenge()
501+
a.Challenges[i] = v.challenge(0)
486502
}
487503
return a
488504
}
@@ -542,6 +558,13 @@ type Challenge struct {
542558
// where the client must send additional data for the server to validate
543559
// the challenge.
544560
Payload json.RawMessage
561+
562+
// RetryAfter specifies how long the client should wait before polling the
563+
// challenge again, based on the Retry-After header provided by the server
564+
// while the challenge is in the StatusProcessing state.
565+
//
566+
// See RFC 8555 Section 8.2.
567+
RetryAfter time.Duration
545568
}
546569

547570
// wireChallenge is ACME JSON challenge representation.
@@ -555,12 +578,13 @@ type wireChallenge struct {
555578
Error *wireError
556579
}
557580

558-
func (c *wireChallenge) challenge() *Challenge {
581+
func (c *wireChallenge) challenge(retryAfter time.Duration) *Challenge {
559582
v := &Challenge{
560-
URI: c.URL,
561-
Type: c.Type,
562-
Token: c.Token,
563-
Status: c.Status,
583+
URI: c.URL,
584+
Type: c.Type,
585+
Token: c.Token,
586+
Status: c.Status,
587+
RetryAfter: retryAfter,
564588
}
565589
if v.URI == "" {
566590
v.URI = c.URI // c.URL was empty; use legacy

0 commit comments

Comments
 (0)