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 <ddos@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
Родитель
74369b46fc
Коммит
a4c6cb3142
23
acme/acme.go
23
acme/acme.go
|
@ -128,11 +128,7 @@ func (c *Client) Discover(ctx context.Context) (Directory, error) {
|
||||||
return *c.dir, nil
|
return *c.dir, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
dirURL := c.DirectoryURL
|
res, err := c.get(ctx, c.directoryURL(), wantStatus(http.StatusOK))
|
||||||
if dirURL == "" {
|
|
||||||
dirURL = LetsEncryptURL
|
|
||||||
}
|
|
||||||
res, err := c.get(ctx, dirURL, wantStatus(http.StatusOK))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Directory{}, err
|
return Directory{}, err
|
||||||
}
|
}
|
||||||
|
@ -165,6 +161,13 @@ func (c *Client) Discover(ctx context.Context) (Directory, error) {
|
||||||
return *c.dir, nil
|
return *c.dir, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) directoryURL() string {
|
||||||
|
if c.DirectoryURL != "" {
|
||||||
|
return c.DirectoryURL
|
||||||
|
}
|
||||||
|
return LetsEncryptURL
|
||||||
|
}
|
||||||
|
|
||||||
// CreateCert requests a new certificate using the Certificate Signing Request csr encoded in DER format.
|
// CreateCert requests a new certificate using the Certificate Signing Request csr encoded in DER format.
|
||||||
// The exp argument indicates the desired certificate validity duration. CA may issue a certificate
|
// The exp argument indicates the desired certificate validity duration. CA may issue a certificate
|
||||||
// with a different duration.
|
// with a different duration.
|
||||||
|
@ -711,12 +714,18 @@ func (c *Client) doReg(ctx context.Context, url string, typ string, acct *Accoun
|
||||||
}
|
}
|
||||||
|
|
||||||
// popNonce returns a nonce value previously stored with c.addNonce
|
// popNonce returns a nonce value previously stored with c.addNonce
|
||||||
// or fetches a fresh one from the given URL.
|
// or fetches a fresh one from a URL by issuing a HEAD request.
|
||||||
|
// It first tries c.directoryURL() and then the provided url if the former fails.
|
||||||
func (c *Client) popNonce(ctx context.Context, url string) (string, error) {
|
func (c *Client) popNonce(ctx context.Context, url string) (string, error) {
|
||||||
c.noncesMu.Lock()
|
c.noncesMu.Lock()
|
||||||
defer c.noncesMu.Unlock()
|
defer c.noncesMu.Unlock()
|
||||||
if len(c.nonces) == 0 {
|
if len(c.nonces) == 0 {
|
||||||
return c.fetchNonce(ctx, url)
|
dirURL := c.directoryURL()
|
||||||
|
v, err := c.fetchNonce(ctx, dirURL)
|
||||||
|
if err != nil && url != dirURL {
|
||||||
|
v, err = c.fetchNonce(ctx, url)
|
||||||
|
}
|
||||||
|
return v, err
|
||||||
}
|
}
|
||||||
var nonce string
|
var nonce string
|
||||||
for nonce = range c.nonces {
|
for nonce = range c.nonces {
|
||||||
|
|
|
@ -75,6 +75,7 @@ func TestDiscover(t *testing.T) {
|
||||||
)
|
)
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Header().Set("Replay-Nonce", "testnonce")
|
||||||
fmt.Fprintf(w, `{
|
fmt.Fprintf(w, `{
|
||||||
"new-reg": %q,
|
"new-reg": %q,
|
||||||
"new-authz": %q,
|
"new-authz": %q,
|
||||||
|
@ -100,6 +101,9 @@ func TestDiscover(t *testing.T) {
|
||||||
if dir.RevokeURL != revoke {
|
if dir.RevokeURL != revoke {
|
||||||
t.Errorf("dir.RevokeURL = %q; want %q", dir.RevokeURL, revoke)
|
t.Errorf("dir.RevokeURL = %q; want %q", dir.RevokeURL, revoke)
|
||||||
}
|
}
|
||||||
|
if _, exist := c.nonces["testnonce"]; !exist {
|
||||||
|
t.Errorf("c.nonces = %q; want 'testnonce' in the map", c.nonces)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRegister(t *testing.T) {
|
func TestRegister(t *testing.T) {
|
||||||
|
@ -147,7 +151,11 @@ func TestRegister(t *testing.T) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
c := Client{Key: testKeyEC, dir: &Directory{RegURL: ts.URL}}
|
c := Client{
|
||||||
|
Key: testKeyEC,
|
||||||
|
DirectoryURL: ts.URL,
|
||||||
|
dir: &Directory{RegURL: ts.URL},
|
||||||
|
}
|
||||||
a := &Account{Contact: contacts}
|
a := &Account{Contact: contacts}
|
||||||
var err error
|
var err error
|
||||||
if a, err = c.Register(context.Background(), a, prompt); err != nil {
|
if a, err = c.Register(context.Background(), a, prompt); err != nil {
|
||||||
|
@ -351,7 +359,11 @@ func TestAuthorize(t *testing.T) {
|
||||||
auth *Authorization
|
auth *Authorization
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
cl := Client{Key: testKeyEC, dir: &Directory{AuthzURL: ts.URL}}
|
cl := Client{
|
||||||
|
Key: testKeyEC,
|
||||||
|
DirectoryURL: ts.URL,
|
||||||
|
dir: &Directory{AuthzURL: ts.URL},
|
||||||
|
}
|
||||||
switch test.typ {
|
switch test.typ {
|
||||||
case "dns":
|
case "dns":
|
||||||
auth, err = cl.Authorize(context.Background(), test.value)
|
auth, err = cl.Authorize(context.Background(), test.value)
|
||||||
|
@ -422,7 +434,11 @@ func TestAuthorizeValid(t *testing.T) {
|
||||||
w.Write([]byte(`{"status":"valid"}`))
|
w.Write([]byte(`{"status":"valid"}`))
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
client := Client{Key: testKey, dir: &Directory{AuthzURL: ts.URL}}
|
client := Client{
|
||||||
|
Key: testKey,
|
||||||
|
DirectoryURL: ts.URL,
|
||||||
|
dir: &Directory{AuthzURL: ts.URL},
|
||||||
|
}
|
||||||
_, err := client.Authorize(context.Background(), "example.com")
|
_, err := client.Authorize(context.Background(), "example.com")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("err = %v", err)
|
t.Errorf("err = %v", err)
|
||||||
|
@ -1037,6 +1053,53 @@ func TestNonce_fetchError(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNonce_popWhenEmpty(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != "HEAD" {
|
||||||
|
t.Errorf("r.Method = %q; want HEAD", r.Method)
|
||||||
|
}
|
||||||
|
switch r.URL.Path {
|
||||||
|
case "/dir-with-nonce":
|
||||||
|
w.Header().Set("Replay-Nonce", "dirnonce")
|
||||||
|
case "/new-nonce":
|
||||||
|
w.Header().Set("Replay-Nonce", "newnonce")
|
||||||
|
case "/dir-no-nonce", "/empty":
|
||||||
|
// No nonce in the header.
|
||||||
|
default:
|
||||||
|
t.Errorf("Unknown URL: %s", r.URL)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
dirURL, popURL, nonce string
|
||||||
|
wantOK bool
|
||||||
|
}{
|
||||||
|
{ts.URL + "/dir-with-nonce", ts.URL + "/new-nonce", "dirnonce", true},
|
||||||
|
{ts.URL + "/dir-no-nonce", ts.URL + "/new-nonce", "newnonce", true},
|
||||||
|
{ts.URL + "/dir-no-nonce", ts.URL + "/empty", "", false},
|
||||||
|
}
|
||||||
|
for _, test := range tt {
|
||||||
|
t.Run(fmt.Sprintf("nonce:%s wantOK:%v", test.nonce, test.wantOK), func(t *testing.T) {
|
||||||
|
c := Client{DirectoryURL: test.dirURL}
|
||||||
|
v, err := c.popNonce(ctx, test.popURL)
|
||||||
|
if !test.wantOK {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("c.popNonce(%q) returned nil error", test.popURL)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("c.popNonce(%q): %v", test.popURL, err)
|
||||||
|
}
|
||||||
|
if v != test.nonce {
|
||||||
|
t.Errorf("c.popNonce(%q) = %q; want %q", test.popURL, v, test.nonce)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNonce_postJWS(t *testing.T) {
|
func TestNonce_postJWS(t *testing.T) {
|
||||||
var count int
|
var count int
|
||||||
seen := make(map[string]bool)
|
seen := make(map[string]bool)
|
||||||
|
@ -1070,7 +1133,11 @@ func TestNonce_postJWS(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
client := Client{Key: testKey, dir: &Directory{AuthzURL: ts.URL}}
|
client := Client{
|
||||||
|
Key: testKey,
|
||||||
|
DirectoryURL: ts.URL, // nonces are fetched from here first
|
||||||
|
dir: &Directory{AuthzURL: ts.URL},
|
||||||
|
}
|
||||||
if _, err := client.Authorize(context.Background(), "example.com"); err != nil {
|
if _, err := client.Authorize(context.Background(), "example.com"); err != nil {
|
||||||
t.Errorf("client.Authorize 1: %v", err)
|
t.Errorf("client.Authorize 1: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,7 +106,11 @@ func TestPostWithRetries(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
client := &Client{Key: testKey, dir: &Directory{AuthzURL: ts.URL}}
|
client := &Client{
|
||||||
|
Key: testKey,
|
||||||
|
DirectoryURL: ts.URL,
|
||||||
|
dir: &Directory{AuthzURL: ts.URL},
|
||||||
|
}
|
||||||
// This call will fail with badNonce, causing a retry
|
// This call will fail with badNonce, causing a retry
|
||||||
if _, err := client.Authorize(context.Background(), "example.com"); err != nil {
|
if _, err := client.Authorize(context.Background(), "example.com"); err != nil {
|
||||||
t.Errorf("client.Authorize 1: %v", err)
|
t.Errorf("client.Authorize 1: %v", err)
|
||||||
|
|
Загрузка…
Ссылка в новой задаче