diff --git a/acme/dns_challenge.go b/acme/dns_challenge.go index bca8d09c..f34fcccc 100644 --- a/acme/dns_challenge.go +++ b/acme/dns_challenge.go @@ -123,3 +123,23 @@ func unFqdn(name string) string { } return name } + +// waitFor polls the given function 'f', once per second, up to 'timeout' seconds. +func waitFor(timeout int, f func() (bool, error)) error { + start := time.Now().Second() + for { + time.Sleep(1 * time.Second) + + if delta := time.Now().Second() - start; delta >= timeout { + return fmt.Errorf("Time limit exceeded (%d seconds)", delta) + } + + stop, err := f() + if err != nil { + return err + } + if stop { + return nil + } + } +} diff --git a/acme/dns_challenge_route53.go b/acme/dns_challenge_route53.go index ba6f3e99..28ed0259 100644 --- a/acme/dns_challenge_route53.go +++ b/acme/dns_challenge_route53.go @@ -43,12 +43,14 @@ func NewDNSProviderRoute53(awsAccessKey, awsSecretKey, awsRegionName string) (*D // Present creates a TXT record using the specified parameters func (r *DNSProviderRoute53) Present(domain, token, keyAuth string) error { fqdn, value, ttl := DNS01Record(domain, keyAuth) + value = `"` + value + `"` return r.changeRecord("UPSERT", fqdn, value, ttl) } // CleanUp removes the TXT record matching the specified parameters func (r *DNSProviderRoute53) CleanUp(domain, token, keyAuth string) error { fqdn, value, ttl := DNS01Record(domain, keyAuth) + value = `"` + value + `"` return r.changeRecord("DELETE", fqdn, value, ttl) } @@ -61,8 +63,21 @@ func (r *DNSProviderRoute53) changeRecord(action, fqdn, value string, ttl int) e update := route53.Change{action, recordSet} changes := []route53.Change{update} req := route53.ChangeResourceRecordSetsRequest{Comment: "Created by Lego", Changes: changes} - _, err = r.client.ChangeResourceRecordSets(hostedZoneID, &req) - return err + resp, err := r.client.ChangeResourceRecordSets(hostedZoneID, &req) + if err != nil { + return err + } + + return waitFor(90, func() (bool, error) { + status, err := r.client.GetChange(resp.ChangeInfo.ID) + if err != nil { + return false, err + } + if status == "INSYNC" { + return true, nil + } + return false, nil + }) } func (r *DNSProviderRoute53) getHostedZoneID(fqdn string) (string, error) { @@ -108,6 +123,7 @@ func newTXTRecordSet(fqdn, value string, ttl int) route53.ResourceRecordSet { Records: []string{value}, TTL: ttl, } + } // Route53 API has pretty strict rate limits (5req/s globally per account) diff --git a/acme/dns_challenge_route53_test.go b/acme/dns_challenge_route53_test.go index 3ab573af..eb551d03 100644 --- a/acme/dns_challenge_route53_test.go +++ b/acme/dns_challenge_route53_test.go @@ -51,9 +51,19 @@ var ListHostedZonesAnswer = ` 100 ` +var GetChangeAnswer = ` + + + /change/asdf + INSYNC + 2016-02-03T01:36:41.958Z + +` + var serverResponseMap = testutil.ResponseMap{ "/2013-04-01/hostedzone/": testutil.Response{200, nil, ListHostedZonesAnswer}, "/2013-04-01/hostedzone/Z2K123214213123/rrset": testutil.Response{200, nil, ChangeResourceRecordSetsAnswer}, + "/2013-04-01/change/asdf": testutil.Response{200, nil, GetChangeAnswer}, } func init() { @@ -112,7 +122,7 @@ func TestRoute53Present(t *testing.T) { assert := assert.New(t) testServer := makeRoute53TestServer() provider := makeRoute53Provider(testServer) - testServer.ResponseMap(2, serverResponseMap) + testServer.ResponseMap(3, serverResponseMap) domain := "example.com" keyAuth := "123456d==" @@ -120,7 +130,7 @@ func TestRoute53Present(t *testing.T) { err := provider.Present(domain, "", keyAuth) assert.NoError(err, "Expected Present to return no error") - httpReqs := testServer.WaitRequests(2) + httpReqs := testServer.WaitRequests(3) httpReq := httpReqs[1] assert.Equal("/2013-04-01/hostedzone/Z2K123214213123/rrset", httpReq.URL.Path,