gcloud: fix for wildcard (#740)

This commit is contained in:
Ludovic Fernandez 2018-12-22 00:53:05 +01:00 committed by GitHub
parent cb3c4c7937
commit 820c2b7531
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 103 additions and 30 deletions

1
Gopkg.lock generated
View file

@ -682,6 +682,7 @@
"golang.org/x/oauth2/clientcredentials", "golang.org/x/oauth2/clientcredentials",
"golang.org/x/oauth2/google", "golang.org/x/oauth2/google",
"google.golang.org/api/dns/v1", "google.golang.org/api/dns/v1",
"google.golang.org/api/googleapi",
"gopkg.in/ns1/ns1-go.v2/rest", "gopkg.in/ns1/ns1-go.v2/rest",
"gopkg.in/ns1/ns1-go.v2/rest/model/dns", "gopkg.in/ns1/ns1-go.v2/rest/model/dns",
"gopkg.in/square/go-jose.v2", "gopkg.in/square/go-jose.v2",

View file

@ -123,7 +123,7 @@ func (c *Challenge) Solve(authz acme.Authorization) error {
log.Infof("[%s] acme: Checking DNS record propagation using %+v", domain, recursiveNameservers) log.Infof("[%s] acme: Checking DNS record propagation using %+v", domain, recursiveNameservers)
err = wait.For(timeout, interval, func() (bool, error) { err = wait.For("propagation", timeout, interval, func() (bool, error) {
stop, errP := c.preCheck.call(fqdn, value) stop, errP := c.preCheck.call(fqdn, value)
if !stop || errP != nil { if !stop || errP != nil {
log.Infof("[%s] acme: Waiting for DNS record propagation.", domain) log.Infof("[%s] acme: Waiting for DNS record propagation.", domain)

View file

@ -91,10 +91,14 @@ func checkAuthoritativeNss(fqdn, value string, nameservers []string) (bool, erro
return false, fmt.Errorf("NS %s returned %s for %s", ns, dns.RcodeToString[r.Rcode], fqdn) return false, fmt.Errorf("NS %s returned %s for %s", ns, dns.RcodeToString[r.Rcode], fqdn)
} }
var records []string
var found bool var found bool
for _, rr := range r.Answer { for _, rr := range r.Answer {
if txt, ok := rr.(*dns.TXT); ok { if txt, ok := rr.(*dns.TXT); ok {
if strings.Join(txt.Txt, "") == value { record := strings.Join(txt.Txt, "")
records = append(records, record)
if record == value {
found = true found = true
break break
} }
@ -102,7 +106,7 @@ func checkAuthoritativeNss(fqdn, value string, nameservers []string) (bool, erro
} }
if !found { if !found {
return false, fmt.Errorf("NS %s did not return the expected TXT record [fqdn: %s]", ns, fqdn) return false, fmt.Errorf("NS %s did not return the expected TXT record [fqdn: %s, value: %s]: %s", ns, fqdn, value, strings.Join(records, " ,"))
} }
} }

View file

@ -141,7 +141,7 @@ func (l *EnvLoader) cmdPebble() (*exec.Cmd, *bytes.Buffer) {
func pebbleHealthCheck(options *CmdOption) { func pebbleHealthCheck(options *CmdOption) {
client := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}} client := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}}
err := wait.For(10*time.Second, 500*time.Millisecond, func() (bool, error) { err := wait.For("pebble", 10*time.Second, 500*time.Millisecond, func() (bool, error) {
resp, err := client.Get(options.HealthCheckURL) resp, err := client.Get(options.HealthCheckURL)
if err != nil { if err != nil {
return false, err return false, err

View file

@ -8,8 +8,8 @@ import (
) )
// For polls the given function 'f', once every 'interval', up to 'timeout'. // For polls the given function 'f', once every 'interval', up to 'timeout'.
func For(timeout, interval time.Duration, f func() (bool, error)) error { func For(msg string, timeout, interval time.Duration, f func() (bool, error)) error {
log.Infof("Wait [timeout: %s, interval: %s]", timeout, interval) log.Infof("Wait for %s [timeout: %s, interval: %s]", msg, timeout, interval)
var lastErr string var lastErr string
timeUp := time.After(timeout) timeUp := time.After(timeout)

View file

@ -8,7 +8,7 @@ import (
func TestForTimeout(t *testing.T) { func TestForTimeout(t *testing.T) {
c := make(chan error) c := make(chan error)
go func() { go func() {
err := For(3*time.Second, 1*time.Second, func() (bool, error) { err := For("", 3*time.Second, 1*time.Second, func() (bool, error) {
return false, nil return false, nil
}) })
c <- err c <- err

View file

@ -8,17 +8,22 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os" "os"
"strconv"
"time" "time"
"github.com/xenolf/lego/challenge/dns01" "github.com/xenolf/lego/challenge/dns01"
"github.com/xenolf/lego/log"
"github.com/xenolf/lego/platform/config/env" "github.com/xenolf/lego/platform/config/env"
"github.com/xenolf/lego/platform/wait"
"golang.org/x/net/context" "golang.org/x/net/context"
"golang.org/x/oauth2/google" "golang.org/x/oauth2/google"
"google.golang.org/api/dns/v1" "google.golang.org/api/dns/v1"
"google.golang.org/api/googleapi"
) )
// Config is used to configure the creation of the DNSProvider // Config is used to configure the creation of the DNSProvider
type Config struct { type Config struct {
Debug bool
Project string Project string
PropagationTimeout time.Duration PropagationTimeout time.Duration
PollingInterval time.Duration PollingInterval time.Duration
@ -29,6 +34,7 @@ type Config struct {
// NewDefaultConfig returns a default configuration for the DNSProvider // NewDefaultConfig returns a default configuration for the DNSProvider
func NewDefaultConfig() *Config { func NewDefaultConfig() *Config {
return &Config{ return &Config{
Debug: env.GetOrDefaultBool("GCE_DEBUG", false),
TTL: env.GetOrDefaultInt("GCE_TTL", dns01.DefaultTTL), TTL: env.GetOrDefaultInt("GCE_TTL", dns01.DefaultTTL),
PropagationTimeout: env.GetOrDefaultSecond("GCE_PROPAGATION_TIMEOUT", 180*time.Second), PropagationTimeout: env.GetOrDefaultSecond("GCE_PROPAGATION_TIMEOUT", 180*time.Second),
PollingInterval: env.GetOrDefaultSecond("GCE_POLLING_INTERVAL", 5*time.Second), PollingInterval: env.GetOrDefaultSecond("GCE_POLLING_INTERVAL", 5*time.Second),
@ -131,11 +137,32 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
} }
// Look for existing records. // Look for existing records.
existing, err := d.findTxtRecords(zone, fqdn) existingRrSet, err := d.findTxtRecords(zone, fqdn)
if err != nil { if err != nil {
return fmt.Errorf("googlecloud: %v", err) return fmt.Errorf("googlecloud: %v", err)
} }
for _, rrSet := range existingRrSet {
var rrd []string
for _, rr := range rrSet.Rrdatas {
data := mustUnquote(rr)
rrd = append(rrd, data)
if data == value {
log.Printf("skip: the record already exists: %s", value)
return nil
}
}
rrSet.Rrdatas = rrd
}
// Attempt to delete the existing records before adding the new one.
if len(existingRrSet) > 0 {
if err = d.applyChanges(zone, &dns.Change{Deletions: existingRrSet}); err != nil {
return fmt.Errorf("googlecloud: %v", err)
}
}
rec := &dns.ResourceRecordSet{ rec := &dns.ResourceRecordSet{
Name: fqdn, Name: fqdn,
Rrdatas: []string{value}, Rrdatas: []string{value},
@ -143,36 +170,69 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
Type: "TXT", Type: "TXT",
} }
change := &dns.Change{} // Append existing TXT record data to the new TXT record data
for _, rrSet := range existingRrSet {
if len(existing) > 0 { for _, rr := range rrSet.Rrdatas {
// Attempt to delete the existing records when adding our new one. if rr != value {
change.Deletions = existing rec.Rrdatas = append(rec.Rrdatas, rrSet.Rrdatas...)
}
// Append existing TXT record data to the new TXT record data
for _, value := range existing {
rec.Rrdatas = append(rec.Rrdatas, value.Rrdatas...)
} }
} }
change.Additions = []*dns.ResourceRecordSet{rec} change := &dns.Change{
Additions: []*dns.ResourceRecordSet{rec},
}
chg, err := d.client.Changes.Create(d.config.Project, zone, change).Do() if err = d.applyChanges(zone, change); err != nil {
if err != nil {
return fmt.Errorf("googlecloud: %v", err) return fmt.Errorf("googlecloud: %v", err)
} }
// wait for change to be acknowledged return nil
for chg.Status == "pending" { }
time.Sleep(time.Second)
chg, err = d.client.Changes.Get(d.config.Project, zone, chg.Id).Do() func (d *DNSProvider) applyChanges(zone string, change *dns.Change) error {
if err != nil { if d.config.Debug {
return fmt.Errorf("googlecloud: %v", err) data, _ := json.Marshal(change)
} log.Printf("change (Create): %s", string(data))
} }
return nil chg, err := d.client.Changes.Create(d.config.Project, zone, change).Do()
if err != nil {
if v, ok := err.(*googleapi.Error); ok {
if v.Code == http.StatusNotFound {
return nil
}
}
data, _ := json.Marshal(change)
return fmt.Errorf("failed to perform changes [zone %s, change %s]: %v", zone, string(data), err)
}
if chg.Status == "done" {
return nil
}
chgID := chg.Id
// wait for change to be acknowledged
return wait.For("apply change", 30*time.Second, 3*time.Second, func() (bool, error) {
if d.config.Debug {
data, _ := json.Marshal(change)
log.Printf("change (Get): %s", string(data))
}
chg, err = d.client.Changes.Get(d.config.Project, zone, chgID).Do()
if err != nil {
data, _ := json.Marshal(change)
return false, fmt.Errorf("failed to get changes [zone %s, change %s]: %v", zone, string(data), err)
}
if chg.Status == "done" {
return true, nil
}
return false, fmt.Errorf("status: %s", chg.Status)
})
} }
// CleanUp removes the TXT record matching the specified parameters. // CleanUp removes the TXT record matching the specified parameters.
@ -236,3 +296,11 @@ func (d *DNSProvider) findTxtRecords(zone, fqdn string) ([]*dns.ResourceRecordSe
return recs.Rrsets, nil return recs.Rrsets, nil
} }
func mustUnquote(raw string) string {
clean, err := strconv.Unquote(raw)
if err != nil {
return raw
}
return clean
}

View file

@ -146,7 +146,7 @@ func (d *DNSProvider) changeRecord(action, fqdn, value, domain string, ttl int)
statusID := resp.ChangeInfo.ID statusID := resp.ChangeInfo.ID
return wait.For(120*time.Second, 4*time.Second, func() (bool, error) { return wait.For("nifcloud", 120*time.Second, 4*time.Second, func() (bool, error) {
resp, err := d.client.GetChange(statusID) resp, err := d.client.GetChange(statusID)
if err != nil { if err != nil {
return false, fmt.Errorf("failed to query NIFCLOUD DNS change status: %v", err) return false, fmt.Errorf("failed to query NIFCLOUD DNS change status: %v", err)

View file

@ -197,7 +197,7 @@ func (d *DNSProvider) changeRecord(action, hostedZoneID string, recordSet *route
changeID := resp.ChangeInfo.Id changeID := resp.ChangeInfo.Id
return wait.For(d.config.PropagationTimeout, d.config.PollingInterval, func() (bool, error) { return wait.For("route53", d.config.PropagationTimeout, d.config.PollingInterval, func() (bool, error) {
reqParams := &route53.GetChangeInput{Id: changeID} reqParams := &route53.GetChangeInput{Id: changeID}
resp, err := d.client.GetChange(reqParams) resp, err := d.client.GetChange(reqParams)