Skip to content

Commit 02abf8d

Browse files
authored
Jitter the ARI Retry-After header by +/-20% (#8576)
Fixes #8558
1 parent 1afad15 commit 02abf8d

3 files changed

Lines changed: 28 additions & 7 deletions

File tree

test/integration/ari_test.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ func TestARIAndReplacement(t *testing.T) {
4545
test.AssertNotError(t, err, "ARI request should have succeeded")
4646
test.AssertEquals(t, ari.SuggestedWindow.Start.Sub(time.Now()).Round(time.Hour), 1418*time.Hour)
4747
test.AssertEquals(t, ari.SuggestedWindow.End.Sub(time.Now()).Round(time.Hour), 1461*time.Hour)
48-
test.AssertEquals(t, ari.RetryAfter.Sub(time.Now()).Round(time.Hour), 6*time.Hour)
48+
lowerRetryAfterLimit := 17280 * time.Second
49+
upperRetryAfterLimit := 25920 * time.Second
50+
retryAfter := ari.RetryAfter.Sub(time.Now()).Round(time.Second)
51+
test.Assert(t, retryAfter >= lowerRetryAfterLimit && retryAfter <= upperRetryAfterLimit, "retry after amount is not within expected range")
4952

5053
// Make a new order which indicates that it replaces the cert issued above,
5154
// and verify that the replacement order succeeds.
@@ -89,7 +92,10 @@ func TestARIShortLived(t *testing.T) {
8992
test.AssertNotError(t, err, "ARI request should have succeeded")
9093
test.AssertEquals(t, ari.SuggestedWindow.Start.Sub(time.Now()).Round(time.Hour), 78*time.Hour)
9194
test.AssertEquals(t, ari.SuggestedWindow.End.Sub(time.Now()).Round(time.Hour), 81*time.Hour)
92-
test.AssertEquals(t, ari.RetryAfter.Sub(time.Now()).Round(time.Hour), 6*time.Hour)
95+
lowerRetryAfterLimit := 17280 * time.Second
96+
upperRetryAfterLimit := 25920 * time.Second
97+
retryAfter := ari.RetryAfter.Sub(time.Now()).Round(time.Second)
98+
test.Assert(t, retryAfter >= lowerRetryAfterLimit && retryAfter <= upperRetryAfterLimit, "retry after amount is not within expected range")
9399
}
94100

95101
func TestARIRevoked(t *testing.T) {

wfe2/wfe.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2718,7 +2718,7 @@ func (wfe *WebFrontEndImpl) RenewalInfo(ctx context.Context, logEvent *web.Reque
27182718
return
27192719
}
27202720

2721-
response.Header().Set(headerRetryAfter, fmt.Sprintf("%d", int(6*time.Hour/time.Second)))
2721+
response.Header().Set(headerRetryAfter, jitterRetryHeader(6*time.Hour))
27222722
err = wfe.writeJsonResponse(response, logEvent, http.StatusOK, renewalInfo)
27232723
if err != nil {
27242724
wfe.sendError(response, logEvent, probs.ServerInternal("Error marshalling renewalInfo"), err)
@@ -2729,3 +2729,12 @@ func (wfe *WebFrontEndImpl) RenewalInfo(ctx context.Context, logEvent *web.Reque
27292729
func urlForAuthz(authz core.Authorization, request *http.Request) string {
27302730
return web.RelativeEndpoint(request, fmt.Sprintf("%s%d/%s", authzPath, authz.RegistrationID, authz.ID))
27312731
}
2732+
2733+
// jitterRetryHeader will return a string formatted random integer of seconds within a 20% window of the
2734+
// duration that is provided.
2735+
func jitterRetryHeader(duration time.Duration) string {
2736+
factor := 0.2 * (2*rand.Float64() - 1)
2737+
jittered := int(float64(duration/time.Second) * (1 + factor))
2738+
2739+
return fmt.Sprintf("%d", jittered)
2740+
}

wfe2/wfe_test.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3863,7 +3863,9 @@ func TestARI(t *testing.T) {
38633863
resp := httptest.NewRecorder()
38643864
wfe.RenewalInfo(context.Background(), event, resp, req)
38653865
test.AssertEquals(t, resp.Code, http.StatusOK)
3866-
test.AssertEquals(t, resp.Header().Get("Retry-After"), "21600")
3866+
retryAfter, err := strconv.Atoi(resp.Header().Get("Retry-After"))
3867+
test.AssertNotError(t, err, "failed to convert retry after header to int")
3868+
test.Assert(t, retryAfter >= 17280 && retryAfter <= 25920, "retry after amount is not within expected range")
38673869
var ri core.RenewalInfo
38683870
err = json.Unmarshal(resp.Body.Bytes(), &ri)
38693871
test.AssertNotError(t, err, "unmarshalling renewal info")
@@ -3877,7 +3879,9 @@ func TestARI(t *testing.T) {
38773879
resp = httptest.NewRecorder()
38783880
wfe.RenewalInfo(context.Background(), event, resp, req)
38793881
test.AssertEquals(t, resp.Code, http.StatusOK)
3880-
test.AssertEquals(t, resp.Header().Get("Retry-After"), "21600")
3882+
retryAfter, err = strconv.Atoi(resp.Header().Get("Retry-After"))
3883+
test.AssertNotError(t, err, "failed to convert retry after header to int")
3884+
test.Assert(t, retryAfter >= 17280 && retryAfter <= 25920, "retry after amount is not within expected range")
38813885
err = json.Unmarshal(resp.Body.Bytes(), &ri)
38823886
test.AssertNotError(t, err, "unmarshalling renewal info")
38833887
test.Assert(t, ri.SuggestedWindow.End.Before(wfe.clk.Now()), "suggested window should end in the past")
@@ -3940,9 +3944,11 @@ func TestIncidentARI(t *testing.T) {
39403944
resp := httptest.NewRecorder()
39413945
wfe.RenewalInfo(context.Background(), event, resp, req)
39423946
test.AssertEquals(t, resp.Code, 200)
3943-
test.AssertEquals(t, resp.Header().Get("Retry-After"), "21600")
3947+
retryAfter, err := strconv.Atoi(resp.Header().Get("Retry-After"))
3948+
test.AssertNotError(t, err, "failed to convert retry after header to int")
3949+
test.Assert(t, retryAfter >= 17280 && retryAfter <= 25920, "retry after amount is not within expected range")
39443950
var ri core.RenewalInfo
3945-
err := json.Unmarshal(resp.Body.Bytes(), &ri)
3951+
err = json.Unmarshal(resp.Body.Bytes(), &ri)
39463952
test.AssertNotError(t, err, "unmarshalling renewal info")
39473953
// The start of the window should be in the past.
39483954
test.AssertEquals(t, ri.SuggestedWindow.Start.Before(wfe.clk.Now()), true)

0 commit comments

Comments
 (0)