From 03665acd94a9d062b40a0f23afb31abce5f2c491 Mon Sep 17 00:00:00 2001 From: Richard Gooch Date: Sat, 10 Apr 2021 11:02:18 -0700 Subject: [PATCH 1/5] Move pkg/loadbalancing/dnslb/route53 to pkg/dns/route53. --- pkg/{loadbalancing/dnslb => dns}/route53/api.go | 0 pkg/{loadbalancing/dnslb => dns}/route53/impl.go | 0 pkg/loadbalancing/dnslb/config/aws.go | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) rename pkg/{loadbalancing/dnslb => dns}/route53/api.go (100%) rename pkg/{loadbalancing/dnslb => dns}/route53/impl.go (100%) diff --git a/pkg/loadbalancing/dnslb/route53/api.go b/pkg/dns/route53/api.go similarity index 100% rename from pkg/loadbalancing/dnslb/route53/api.go rename to pkg/dns/route53/api.go diff --git a/pkg/loadbalancing/dnslb/route53/impl.go b/pkg/dns/route53/impl.go similarity index 100% rename from pkg/loadbalancing/dnslb/route53/impl.go rename to pkg/dns/route53/impl.go diff --git a/pkg/loadbalancing/dnslb/config/aws.go b/pkg/loadbalancing/dnslb/config/aws.go index d174f96..d0ae05d 100644 --- a/pkg/loadbalancing/dnslb/config/aws.go +++ b/pkg/loadbalancing/dnslb/config/aws.go @@ -6,9 +6,9 @@ import ( "time" "github.com/Cloud-Foundations/golib/pkg/awsutil/metadata" + "github.com/Cloud-Foundations/golib/pkg/dns/route53" "github.com/Cloud-Foundations/golib/pkg/loadbalancing/dnslb" "github.com/Cloud-Foundations/golib/pkg/loadbalancing/dnslb/ec2" - "github.com/Cloud-Foundations/golib/pkg/loadbalancing/dnslb/route53" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" ) From 47b1e75423a853c15a5e8e17798cd68a33c29265 Mon Sep 17 00:00:00 2001 From: Richard Gooch Date: Sun, 11 Apr 2021 10:37:49 -0700 Subject: [PATCH 2/5] pkg/dns/route53: wait for changes to propagate to all servers. --- pkg/dns/route53/impl.go | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/pkg/dns/route53/impl.go b/pkg/dns/route53/impl.go index 92c1291..7e14927 100644 --- a/pkg/dns/route53/impl.go +++ b/pkg/dns/route53/impl.go @@ -38,6 +38,31 @@ func stripQuotes(value string) string { return value } +func waitForChange(awsService *route53.Route53, id *string, + logger log.DebugLogger) error { + timer := time.NewTimer(time.Minute * 2) + errorChannel := make(chan error, 1) + go func() { + errorChannel <- awsService.WaitUntilResourceRecordSetsChanged( + &route53.GetChangeInput{Id: id}) + }() + select { + case <-timer.C: + output, err := awsService.GetChange(&route53.GetChangeInput{Id: id}) + if err != nil { + logger.Printf("timed out waiting for change: %s, hoping for the best, error from GetChange(): %s\n", + *id, err) + return nil + } + logger.Printf( + "timed out waiting for change: %s, hoping for the best, status: %s\n", + id, *output.ChangeInfo.Status) + return nil + case err := <-errorChannel: + return err + } +} + func (rrw *RecordReadWriter) deleteRecords(fqdn, recType string) error { if fqdn[len(fqdn)-1] != '.' { fqdn += "." @@ -127,6 +152,16 @@ func (rrw *RecordReadWriter) writeRecords(fqdn, recType string, }, HostedZoneId: rrw.hostedZoneId, } - _, err := rrw.awsService.ChangeResourceRecordSets(input) + output, err := rrw.awsService.ChangeResourceRecordSets(input) + if err != nil { + return err + } + rrw.logger.Debugf(1, "waiting for change: %s to complete\n", + *output.ChangeInfo.Id) + err = waitForChange(rrw.awsService, output.ChangeInfo.Id, rrw.logger) + if err != nil { + return err + } + rrw.logger.Debugf(1, "change: %s completed\n", *output.ChangeInfo.Id) return err } From 5f5befd097544e947f9713e80faa286c4cae8258 Mon Sep 17 00:00:00 2001 From: Richard Gooch Date: Mon, 12 Apr 2021 09:42:12 -0700 Subject: [PATCH 3/5] Make use of pkg/dns/route53 in pkg/crypto/certmanager/dns/route53. --- pkg/crypto/certmanager/dns/route53/api.go | 13 +++- pkg/crypto/certmanager/dns/route53/impl.go | 87 ++++------------------ 2 files changed, 24 insertions(+), 76 deletions(-) diff --git a/pkg/crypto/certmanager/dns/route53/api.go b/pkg/crypto/certmanager/dns/route53/api.go index 7a49507..d4ce580 100644 --- a/pkg/crypto/certmanager/dns/route53/api.go +++ b/pkg/crypto/certmanager/dns/route53/api.go @@ -4,14 +4,19 @@ Package route53 implements a dns-01 ACME protocol responder using AWS Route 53. package route53 import ( + "time" + "github.com/Cloud-Foundations/golib/pkg/log" - "github.com/aws/aws-sdk-go/service/route53" ) +type recordDeleteWriter interface { + DeleteRecords(fqdn, recType string) error + WriteRecords(fqdn, recType string, recs []string, ttl time.Duration) error +} + type Responder struct { - awsService *route53.Route53 - hostedZoneId *string - logger log.DebugLogger + rdw recordDeleteWriter + logger log.DebugLogger // Mutable data follow. records map[string]string } diff --git a/pkg/crypto/certmanager/dns/route53/impl.go b/pkg/crypto/certmanager/dns/route53/impl.go index e6e1399..b4c2e14 100644 --- a/pkg/crypto/certmanager/dns/route53/impl.go +++ b/pkg/crypto/certmanager/dns/route53/impl.go @@ -4,53 +4,12 @@ import ( "errors" "time" + "github.com/Cloud-Foundations/golib/pkg/dns/route53" "github.com/Cloud-Foundations/golib/pkg/log" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/route53" ) -// const defaultRegion = "us-west-2" - -func makeTXT(action, fqdn, txtValue string) *route53.Change { - return &route53.Change{ - Action: aws.String(action), - ResourceRecordSet: &route53.ResourceRecordSet{ - Name: aws.String(fqdn), - ResourceRecords: []*route53.ResourceRecord{{ - Value: aws.String(`"` + txtValue + `"`), - }}, - TTL: aws.Int64(15), - Type: aws.String("TXT"), - }, - } -} - -func waitForChange(awsService *route53.Route53, id *string, - logger log.DebugLogger) error { - timer := time.NewTimer(time.Minute * 2) - errorChannel := make(chan error, 1) - go func() { - errorChannel <- awsService.WaitUntilResourceRecordSetsChanged( - &route53.GetChangeInput{Id: id}) - }() - select { - case <-timer.C: - output, err := awsService.GetChange(&route53.GetChangeInput{Id: id}) - if err != nil { - logger.Printf("timed out waiting for change: %s, hoping for the best, error from GetChange(): %s\n", - *id, err) - return nil - } - logger.Printf( - "timed out waiting for change: %s, hoping for the best, status: %s\n", - id, *output.ChangeInfo.Status) - return nil - case err := <-errorChannel: - return err - } -} - func newResponder(hostedZoneId string, logger log.DebugLogger) (*Responder, error) { if hostedZoneId == "" { @@ -63,11 +22,14 @@ func newResponder(hostedZoneId string, if awsSession == nil { return nil, errors.New("awsSession == nil") } + rdw, err := route53.New(awsSession, hostedZoneId, logger) + if err != nil { + return nil, err + } return &Responder{ - awsService: route53.New(awsSession), - hostedZoneId: aws.String(hostedZoneId), - logger: logger, - records: make(map[string]string), + rdw: rdw, + logger: logger, + records: make(map[string]string), }, nil } @@ -75,19 +37,13 @@ func (r *Responder) cleanup() { if len(r.records) < 1 { return } - changeBatch := make([]*route53.Change, 0, len(r.records)) - for fqdn, txtValue := range r.records { - changeBatch = append(changeBatch, makeTXT("DELETE", fqdn, txtValue)) - } - input := route53.ChangeResourceRecordSetsInput{ - ChangeBatch: &route53.ChangeBatch{Changes: changeBatch}, - HostedZoneId: r.hostedZoneId, - } - _, err := r.awsService.ChangeResourceRecordSets(&input) - if err != nil { - return + for fqdn := range r.records { + if err := r.rdw.DeleteRecords(fqdn, "TXT"); err != nil { + r.logger.Println(err) + } else { + delete(r.records, fqdn) + } } - r.records = make(map[string]string) } func (r *Responder) respond(key, value string) error { @@ -95,23 +51,10 @@ func (r *Responder) respond(key, value string) error { return nil } r.logger.Debugf(1, "publishing %s TXT=\"%s\"\n", key, value) - input := route53.ChangeResourceRecordSetsInput{ - ChangeBatch: &route53.ChangeBatch{Changes: []*route53.Change{ - makeTXT("UPSERT", key, value)}, - }, - HostedZoneId: r.hostedZoneId, - } - output, err := r.awsService.ChangeResourceRecordSets(&input) - if err != nil { - return err - } - r.logger.Debugf(1, "waiting for change: %s to complete\n", - *output.ChangeInfo.Id) - err = waitForChange(r.awsService, output.ChangeInfo.Id, r.logger) + err := r.rdw.WriteRecords(key, "TXT", []string{value}, time.Second*15) if err != nil { return err } r.records[key] = value - r.logger.Debugf(1, "change: %s completed\n", *output.ChangeInfo.Id) return nil } From 8a5595b631d916599a7eb6efb643eb157c973b45 Mon Sep 17 00:00:00 2001 From: Richard Gooch Date: Tue, 13 Apr 2021 07:04:01 -0700 Subject: [PATCH 4/5] Add pkg/crypto/certmanager.MakeDnsResponder(). --- pkg/crypto/certmanager/api.go | 12 ++++++++++ pkg/crypto/certmanager/dns.go | 45 +++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/pkg/crypto/certmanager/api.go b/pkg/crypto/certmanager/api.go index 0479d18..0ec1e31 100644 --- a/pkg/crypto/certmanager/api.go +++ b/pkg/crypto/certmanager/api.go @@ -61,6 +61,12 @@ type CertificateManager struct { certificate *Certificate } +// DnsRecordDeleteWriter is an interface to a DNS record manager. +type DnsRecordDeleteWriter interface { + DeleteRecords(fqdn, recType string) error + WriteRecords(fqdn, recType string, recs []string, ttl time.Duration) error +} + type keyMakerFunc func() (crypto.Signer, error) // Locker is an interface to a remote locking mechanism. @@ -136,3 +142,9 @@ func (cm *CertificateManager) GetCertificate(hello *tls.ClientHelloInfo) ( func (cm *CertificateManager) GetWriteNotifier() <-chan struct{} { return cm.writeNotifier } + +// MakeDnsResponder will create a dns-01 Responder from a DNS record manager. +func MakeDnsResponder(rdw DnsRecordDeleteWriter, + logger log.DebugLogger) (Responder, error) { + return makeDnsResponder(rdw, logger) +} diff --git a/pkg/crypto/certmanager/dns.go b/pkg/crypto/certmanager/dns.go index 9c54b88..32feaee 100644 --- a/pkg/crypto/certmanager/dns.go +++ b/pkg/crypto/certmanager/dns.go @@ -2,8 +2,18 @@ package certmanager import ( "golang.org/x/crypto/acme" + "time" + + "github.com/Cloud-Foundations/golib/pkg/log" ) +type dnsResponder struct { + rdw DnsRecordDeleteWriter + logger log.DebugLogger + // Mutable data follow. + records map[string]string +} + func (cm *CertificateManager) respondDNS(domain string, challenge *acme.Challenge) error { response, err := cm.acmeClient.DNS01ChallengeRecord(challenge.Token) @@ -12,3 +22,38 @@ func (cm *CertificateManager) respondDNS(domain string, } return cm.responder.Respond("_acme-challenge."+domain, response) } + +func makeDnsResponder(rdw DnsRecordDeleteWriter, + logger log.DebugLogger) (Responder, error) { + return &dnsResponder{ + rdw: rdw, + logger: logger, + records: make(map[string]string), + }, nil +} + +func (r *dnsResponder) Cleanup() { + if len(r.records) < 1 { + return + } + for fqdn := range r.records { + if err := r.rdw.DeleteRecords(fqdn, "TXT"); err != nil { + r.logger.Println(err) + } else { + delete(r.records, fqdn) + } + } +} + +func (r *dnsResponder) Respond(key, value string) error { + if r.records[key] == value { + return nil + } + r.logger.Debugf(1, "publishing %s TXT=\"%s\"\n", key, value) + err := r.rdw.WriteRecords(key, "TXT", []string{value}, time.Second*15) + if err != nil { + return err + } + r.records[key] = value + return nil +} From 905265c3e5cd95b1a2d19943a38de8ffaf958de1 Mon Sep 17 00:00:00 2001 From: Richard Gooch Date: Wed, 14 Apr 2021 08:18:27 -0700 Subject: [PATCH 5/5] Move pkg/crypto/certmanager/dns/route53 generic code. --- pkg/crypto/certmanager/dns/route53/api.go | 25 ++------------- pkg/crypto/certmanager/dns/route53/impl.go | 36 ++-------------------- 2 files changed, 5 insertions(+), 56 deletions(-) diff --git a/pkg/crypto/certmanager/dns/route53/api.go b/pkg/crypto/certmanager/dns/route53/api.go index d4ce580..cdee136 100644 --- a/pkg/crypto/certmanager/dns/route53/api.go +++ b/pkg/crypto/certmanager/dns/route53/api.go @@ -4,34 +4,13 @@ Package route53 implements a dns-01 ACME protocol responder using AWS Route 53. package route53 import ( - "time" - + "github.com/Cloud-Foundations/golib/pkg/crypto/certmanager" "github.com/Cloud-Foundations/golib/pkg/log" ) -type recordDeleteWriter interface { - DeleteRecords(fqdn, recType string) error - WriteRecords(fqdn, recType string, recs []string, ttl time.Duration) error -} - -type Responder struct { - rdw recordDeleteWriter - logger log.DebugLogger - // Mutable data follow. - records map[string]string -} - // New creates a DNS responder for ACME dns-01 challenges. // The logger is used for logging messages. func New(hostedZoneId string, - logger log.DebugLogger) (*Responder, error) { + logger log.DebugLogger) (certmanager.Responder, error) { return newResponder(hostedZoneId, logger) } - -func (r *Responder) Cleanup() { - r.cleanup() -} - -func (r *Responder) Respond(key, value string) error { - return r.respond(key, value) -} diff --git a/pkg/crypto/certmanager/dns/route53/impl.go b/pkg/crypto/certmanager/dns/route53/impl.go index b4c2e14..23530c3 100644 --- a/pkg/crypto/certmanager/dns/route53/impl.go +++ b/pkg/crypto/certmanager/dns/route53/impl.go @@ -2,8 +2,8 @@ package route53 import ( "errors" - "time" + "github.com/Cloud-Foundations/golib/pkg/crypto/certmanager" "github.com/Cloud-Foundations/golib/pkg/dns/route53" "github.com/Cloud-Foundations/golib/pkg/log" "github.com/aws/aws-sdk-go/aws" @@ -11,7 +11,7 @@ import ( ) func newResponder(hostedZoneId string, - logger log.DebugLogger) (*Responder, error) { + logger log.DebugLogger) (certmanager.Responder, error) { if hostedZoneId == "" { return nil, errors.New("no hosted zone ID specified") } @@ -26,35 +26,5 @@ func newResponder(hostedZoneId string, if err != nil { return nil, err } - return &Responder{ - rdw: rdw, - logger: logger, - records: make(map[string]string), - }, nil -} - -func (r *Responder) cleanup() { - if len(r.records) < 1 { - return - } - for fqdn := range r.records { - if err := r.rdw.DeleteRecords(fqdn, "TXT"); err != nil { - r.logger.Println(err) - } else { - delete(r.records, fqdn) - } - } -} - -func (r *Responder) respond(key, value string) error { - if r.records[key] == value { - return nil - } - r.logger.Debugf(1, "publishing %s TXT=\"%s\"\n", key, value) - err := r.rdw.WriteRecords(key, "TXT", []string{value}, time.Second*15) - if err != nil { - return err - } - r.records[key] = value - return nil + return certmanager.MakeDnsResponder(rdw, logger) }