From 611a597ae0eb113ca2bf5ddafdddfb17762b511e Mon Sep 17 00:00:00 2001 From: Tom Limoncelli Date: Wed, 20 Dec 2017 10:25:23 -0500 Subject: [PATCH] CLOUDFLARE: Support CAA rtype (#285) * Add CAA support to cloudflare --- docs/_includes/matrix.html | 4 +- models/dns.go | 38 +++++++++++++++++++ providers/cloudflare/cloudflareProvider.go | 44 +++++++++++++++------- providers/cloudflare/rest.go | 18 +++++++++ providers/vultr/vultrProvider.go | 5 +++ 5 files changed, 95 insertions(+), 14 deletions(-) diff --git a/docs/_includes/matrix.html b/docs/_includes/matrix.html index 232cac93f..c20ebd0ba 100644 --- a/docs/_includes/matrix.html +++ b/docs/_includes/matrix.html @@ -294,7 +294,9 @@ - + + + diff --git a/models/dns.go b/models/dns.go index 577c9499b..2096d880f 100644 --- a/models/dns.go +++ b/models/dns.go @@ -60,6 +60,18 @@ type DNSProviderConfig struct { // NameFQDN: // This is the FQDN version of Name. // It should never have a trailiing ".". +// Valid types: +// A +// AAAA +// ANAME +// CAA +// CNAME +// MX +// NS +// SRV +// TXT +// PAGE_RULE // pseudo rtype +// TODO(tal): Add all the pseudo types type RecordConfig struct { Type string `json:"type"` Name string `json:"name"` // The short name. See below. @@ -418,6 +430,32 @@ func SplitCombinedSrvValue(s string) (priority, weight, port uint16, target stri return uint16(priorityconv), uint16(weightconv), uint16(portconv), parts[3], nil } +func SplitCombinedCaaValue(s string) (tag string, flag uint8, value string, err error) { + + splitData := strings.SplitN(s, " ", 3) + if len(splitData) != 3 { + err = fmt.Errorf("Unexpected data for CAA record returned by Vultr") + return + } + + lflag, err := strconv.ParseUint(splitData[0], 10, 8) + if err != nil { + return + } + flag = uint8(lflag) + + tag = splitData[1] + + value = splitData[2] + if strings.HasPrefix(value, `"`) && strings.HasSuffix(value, `"`) { + value = value[1 : len(value)-1] + } + if strings.HasPrefix(value, `'`) && strings.HasSuffix(value, `'`) { + value = value[1 : len(value)-1] + } + return +} + func copyObj(input interface{}, output interface{}) error { buf := &bytes.Buffer{} enc := gob.NewEncoder(buf) diff --git a/providers/cloudflare/cloudflareProvider.go b/providers/cloudflare/cloudflareProvider.go index b107ad49f..032ae95c7 100644 --- a/providers/cloudflare/cloudflareProvider.go +++ b/providers/cloudflare/cloudflareProvider.go @@ -41,7 +41,7 @@ var docNotes = providers.DocumentationNotes{ } func init() { - providers.RegisterDomainServiceProviderType("CLOUDFLAREAPI", newCloudflare, providers.CanUseSRV, providers.CanUseAlias, docNotes) + providers.RegisterDomainServiceProviderType("CLOUDFLAREAPI", newCloudflare, providers.CanUseSRV, providers.CanUseAlias, providers.CanUseCAA, docNotes) providers.RegisterCustomRecordType("CF_REDIRECT", "CLOUDFLAREAPI", "") providers.RegisterCustomRecordType("CF_TEMP_REDIRECT", "CLOUDFLAREAPI", "") } @@ -333,13 +333,16 @@ func newCloudflare(m map[string]string, metadata json.RawMessage) (providers.DNS // Used on the "existing" records. type cfRecData struct { - Service string `json:"service"` - Proto string `json:"proto"` Name string `json:"name"` - Priority uint16 `json:"priority"` - Weight uint16 `json:"weight"` - Port uint16 `json:"port"` Target string `json:"target"` + Service string `json:"service"` // SRV + Proto string `json:"proto"` // SRV + Priority uint16 `json:"priority"` // SRV + Weight uint16 `json:"weight"` // SRV + Port uint16 `json:"port"` // SRV + Tag string `json:"tag"` // CAA + Flags uint8 `json:"flags"` // CAA + Value string `json:"value"` // CAA } type cfRecord struct { @@ -365,20 +368,35 @@ func (c *cfRecord) toRecord(domain string) *models.RecordConfig { c.Content = dnsutil.AddOrigin(c.Content+".", domain) } rc := &models.RecordConfig{ - NameFQDN: c.Name, - Type: c.Type, - Target: c.Content, - MxPreference: c.Priority, - TTL: c.TTL, - Original: c, + NameFQDN: c.Name, + Type: c.Type, + Target: c.Content, + TTL: c.TTL, + Original: c, } - if c.Type == "SRV" { + switch c.Type { // #rtype_variations + case "A", "AAAA", "ANAME", "CNAME", "NS", "TXT": + // nothing additional needed. + case "CAA": + var err error + rc.CaaTag, rc.CaaFlag, rc.Target, err = models.SplitCombinedCaaValue(c.Content) + if err != nil { + panic(err) + } + case "MX": + rc.MxPreference = c.Priority + case "SRV": data := *c.Data rc.SrvPriority = data.Priority rc.SrvWeight = data.Weight rc.SrvPort = data.Port rc.Target = dnsutil.AddOrigin(data.Target+".", domain) + default: + panic(fmt.Sprintf("toRecord unimplemented rtype %v", c.Type)) + // We panic so that we quickly find any switch statements + // that have not been updated for a new RR type. } + return rc } diff --git a/providers/cloudflare/rest.go b/providers/cloudflare/rest.go index 1601ce902..6541d2baf 100644 --- a/providers/cloudflare/rest.go +++ b/providers/cloudflare/rest.go @@ -67,6 +67,7 @@ func (c *CloudflareApi) getRecordsForDomain(id string, domain string) ([]*models return nil, fmt.Errorf("Error fetching record list cloudflare: %s", stringifyErrors(data.Errors)) } for _, rec := range data.Result { + //fmt.Printf("REC: %+v\n", rec) records = append(records, rec.toRecord(domain)) } ri := data.ResultInfo @@ -75,6 +76,7 @@ func (c *CloudflareApi) getRecordsForDomain(id string, domain string) ([]*models } page++ } + //fmt.Printf("DEBUG REORDS=%v\n", records) return records, nil } @@ -129,6 +131,14 @@ func cfSrvData(rec *models.RecordConfig) *cfRecData { } } +func cfCaaData(rec *models.RecordConfig) *cfRecData { + return &cfRecData{ + Tag: rec.CaaTag, + Flags: rec.CaaFlag, + Value: rec.Target, + } +} + func (c *CloudflareApi) createRec(rec *models.RecordConfig, domainID string) []*models.Correction { type createRecord struct { Name string `json:"name"` @@ -161,6 +171,10 @@ func (c *CloudflareApi) createRec(rec *models.RecordConfig, domainID string) []* if rec.Type == "SRV" { cf.Data = cfSrvData(rec) cf.Name = rec.NameFQDN + } else if rec.Type == "CAA" { + cf.Data = cfCaaData(rec) + cf.Name = rec.NameFQDN + cf.Content = "" } endpoint := fmt.Sprintf(recordsURL, domainID) buf := &bytes.Buffer{} @@ -204,6 +218,10 @@ func (c *CloudflareApi) modifyRecord(domainID, recID string, proxied bool, rec * if rec.Type == "SRV" { r.Data = cfSrvData(rec) r.Name = rec.NameFQDN + } else if rec.Type == "CAA" { + r.Data = cfCaaData(rec) + r.Name = rec.NameFQDN + r.Content = "" } endpoint := fmt.Sprintf(singleRecordURL, domainID, recID) buf := &bytes.Buffer{} diff --git a/providers/vultr/vultrProvider.go b/providers/vultr/vultrProvider.go index 84f5d8066..afc20c623 100644 --- a/providers/vultr/vultrProvider.go +++ b/providers/vultr/vultrProvider.go @@ -255,6 +255,11 @@ func toRecordConfig(dc *models.DomainConfig, r *vultr.DNSRecord) (*models.Record if r.Type == "CAA" { // Vultr returns in the format "[flag] [tag] [value]" + // TODO(tal): I copied this code into models/dns.go. At this point + // we can probably replace the code below with: + // rc.CaaFlag, rc.CaaTag, rc.Target, err := models.SplitCombinedCaaValue(rc.Target) + // return rc, err + splitData := strings.SplitN(rc.Target, " ", 3) if len(splitData) != 3 { return nil, fmt.Errorf("Unexpected data for CAA record returned by Vultr")