Skip to content

Commit a4c6cb3

Browse files
author
Alex Vaghin
committed
acme: try to fetch nonce from directory first
The change should reduce resource quota consumed by the client overall. Instead of sending HEAD to an ACME resource URL to get a new nonce, the Client will now try to fetch it from the Directory URL first and only then from the ACME resource URL if the former fails. This builds up on an abandoned https://golang.org/cl/34623, only this time with a fallback to the original behaviour. Change-Id: I6e75c0e524c4bc751f3a651b290c0ac2493e0628 Reviewed-on: https://go-review.googlesource.com/c/162057 Run-TryBot: Alex Vaghin <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent 74369b4 commit a4c6cb3

File tree

3 files changed

+92
-12
lines changed

3 files changed

+92
-12
lines changed

acme/acme.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,7 @@ func (c *Client) Discover(ctx context.Context) (Directory, error) {
128128
return *c.dir, nil
129129
}
130130

131-
dirURL := c.DirectoryURL
132-
if dirURL == "" {
133-
dirURL = LetsEncryptURL
134-
}
135-
res, err := c.get(ctx, dirURL, wantStatus(http.StatusOK))
131+
res, err := c.get(ctx, c.directoryURL(), wantStatus(http.StatusOK))
136132
if err != nil {
137133
return Directory{}, err
138134
}
@@ -165,6 +161,13 @@ func (c *Client) Discover(ctx context.Context) (Directory, error) {
165161
return *c.dir, nil
166162
}
167163

164+
func (c *Client) directoryURL() string {
165+
if c.DirectoryURL != "" {
166+
return c.DirectoryURL
167+
}
168+
return LetsEncryptURL
169+
}
170+
168171
// CreateCert requests a new certificate using the Certificate Signing Request csr encoded in DER format.
169172
// The exp argument indicates the desired certificate validity duration. CA may issue a certificate
170173
// with a different duration.
@@ -711,12 +714,18 @@ func (c *Client) doReg(ctx context.Context, url string, typ string, acct *Accoun
711714
}
712715

713716
// popNonce returns a nonce value previously stored with c.addNonce
714-
// or fetches a fresh one from the given URL.
717+
// or fetches a fresh one from a URL by issuing a HEAD request.
718+
// It first tries c.directoryURL() and then the provided url if the former fails.
715719
func (c *Client) popNonce(ctx context.Context, url string) (string, error) {
716720
c.noncesMu.Lock()
717721
defer c.noncesMu.Unlock()
718722
if len(c.nonces) == 0 {
719-
return c.fetchNonce(ctx, url)
723+
dirURL := c.directoryURL()
724+
v, err := c.fetchNonce(ctx, dirURL)
725+
if err != nil && url != dirURL {
726+
v, err = c.fetchNonce(ctx, url)
727+
}
728+
return v, err
720729
}
721730
var nonce string
722731
for nonce = range c.nonces {

acme/acme_test.go

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ func TestDiscover(t *testing.T) {
7575
)
7676
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
7777
w.Header().Set("Content-Type", "application/json")
78+
w.Header().Set("Replay-Nonce", "testnonce")
7879
fmt.Fprintf(w, `{
7980
"new-reg": %q,
8081
"new-authz": %q,
@@ -100,6 +101,9 @@ func TestDiscover(t *testing.T) {
100101
if dir.RevokeURL != revoke {
101102
t.Errorf("dir.RevokeURL = %q; want %q", dir.RevokeURL, revoke)
102103
}
104+
if _, exist := c.nonces["testnonce"]; !exist {
105+
t.Errorf("c.nonces = %q; want 'testnonce' in the map", c.nonces)
106+
}
103107
}
104108

105109
func TestRegister(t *testing.T) {
@@ -147,7 +151,11 @@ func TestRegister(t *testing.T) {
147151
return false
148152
}
149153

150-
c := Client{Key: testKeyEC, dir: &Directory{RegURL: ts.URL}}
154+
c := Client{
155+
Key: testKeyEC,
156+
DirectoryURL: ts.URL,
157+
dir: &Directory{RegURL: ts.URL},
158+
}
151159
a := &Account{Contact: contacts}
152160
var err error
153161
if a, err = c.Register(context.Background(), a, prompt); err != nil {
@@ -351,7 +359,11 @@ func TestAuthorize(t *testing.T) {
351359
auth *Authorization
352360
err error
353361
)
354-
cl := Client{Key: testKeyEC, dir: &Directory{AuthzURL: ts.URL}}
362+
cl := Client{
363+
Key: testKeyEC,
364+
DirectoryURL: ts.URL,
365+
dir: &Directory{AuthzURL: ts.URL},
366+
}
355367
switch test.typ {
356368
case "dns":
357369
auth, err = cl.Authorize(context.Background(), test.value)
@@ -422,7 +434,11 @@ func TestAuthorizeValid(t *testing.T) {
422434
w.Write([]byte(`{"status":"valid"}`))
423435
}))
424436
defer ts.Close()
425-
client := Client{Key: testKey, dir: &Directory{AuthzURL: ts.URL}}
437+
client := Client{
438+
Key: testKey,
439+
DirectoryURL: ts.URL,
440+
dir: &Directory{AuthzURL: ts.URL},
441+
}
426442
_, err := client.Authorize(context.Background(), "example.com")
427443
if err != nil {
428444
t.Errorf("err = %v", err)
@@ -1037,6 +1053,53 @@ func TestNonce_fetchError(t *testing.T) {
10371053
}
10381054
}
10391055

1056+
func TestNonce_popWhenEmpty(t *testing.T) {
1057+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1058+
if r.Method != "HEAD" {
1059+
t.Errorf("r.Method = %q; want HEAD", r.Method)
1060+
}
1061+
switch r.URL.Path {
1062+
case "/dir-with-nonce":
1063+
w.Header().Set("Replay-Nonce", "dirnonce")
1064+
case "/new-nonce":
1065+
w.Header().Set("Replay-Nonce", "newnonce")
1066+
case "/dir-no-nonce", "/empty":
1067+
// No nonce in the header.
1068+
default:
1069+
t.Errorf("Unknown URL: %s", r.URL)
1070+
}
1071+
}))
1072+
defer ts.Close()
1073+
ctx := context.Background()
1074+
1075+
tt := []struct {
1076+
dirURL, popURL, nonce string
1077+
wantOK bool
1078+
}{
1079+
{ts.URL + "/dir-with-nonce", ts.URL + "/new-nonce", "dirnonce", true},
1080+
{ts.URL + "/dir-no-nonce", ts.URL + "/new-nonce", "newnonce", true},
1081+
{ts.URL + "/dir-no-nonce", ts.URL + "/empty", "", false},
1082+
}
1083+
for _, test := range tt {
1084+
t.Run(fmt.Sprintf("nonce:%s wantOK:%v", test.nonce, test.wantOK), func(t *testing.T) {
1085+
c := Client{DirectoryURL: test.dirURL}
1086+
v, err := c.popNonce(ctx, test.popURL)
1087+
if !test.wantOK {
1088+
if err == nil {
1089+
t.Fatalf("c.popNonce(%q) returned nil error", test.popURL)
1090+
}
1091+
return
1092+
}
1093+
if err != nil {
1094+
t.Fatalf("c.popNonce(%q): %v", test.popURL, err)
1095+
}
1096+
if v != test.nonce {
1097+
t.Errorf("c.popNonce(%q) = %q; want %q", test.popURL, v, test.nonce)
1098+
}
1099+
})
1100+
}
1101+
}
1102+
10401103
func TestNonce_postJWS(t *testing.T) {
10411104
var count int
10421105
seen := make(map[string]bool)
@@ -1070,7 +1133,11 @@ func TestNonce_postJWS(t *testing.T) {
10701133
}))
10711134
defer ts.Close()
10721135

1073-
client := Client{Key: testKey, dir: &Directory{AuthzURL: ts.URL}}
1136+
client := Client{
1137+
Key: testKey,
1138+
DirectoryURL: ts.URL, // nonces are fetched from here first
1139+
dir: &Directory{AuthzURL: ts.URL},
1140+
}
10741141
if _, err := client.Authorize(context.Background(), "example.com"); err != nil {
10751142
t.Errorf("client.Authorize 1: %v", err)
10761143
}

acme/http_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,11 @@ func TestPostWithRetries(t *testing.T) {
106106
}))
107107
defer ts.Close()
108108

109-
client := &Client{Key: testKey, dir: &Directory{AuthzURL: ts.URL}}
109+
client := &Client{
110+
Key: testKey,
111+
DirectoryURL: ts.URL,
112+
dir: &Directory{AuthzURL: ts.URL},
113+
}
110114
// This call will fail with badNonce, causing a retry
111115
if _, err := client.Authorize(context.Background(), "example.com"); err != nil {
112116
t.Errorf("client.Authorize 1: %v", err)

0 commit comments

Comments
 (0)