From 02d76affc0b7c39442804d9bcb0485e114fd811b Mon Sep 17 00:00:00 2001 From: Koen Vlaswinkel Date: Fri, 25 Mar 2022 20:22:20 +0100 Subject: [PATCH] LINODE: Add support for CAA records and implement get-zones (#1454) * LINODE: Add support for CAA records * LINODE: Implement get-zones Co-authored-by: Tom Limoncelli --- docs/_includes/matrix.html | 8 ++- providers/linode/api.go | 9 +-- providers/linode/linodeProvider.go | 92 +++++++++++++++++++----------- 3 files changed, 68 insertions(+), 41 deletions(-) diff --git a/docs/_includes/matrix.html b/docs/_includes/matrix.html index ca30b946a..3e6de9fe7 100644 --- a/docs/_includes/matrix.html +++ b/docs/_includes/matrix.html @@ -627,7 +627,9 @@ - + + + @@ -1819,8 +1821,8 @@ - - + + diff --git a/providers/linode/api.go b/providers/linode/api.go index 59f72c093..496a64537 100644 --- a/providers/linode/api.go +++ b/providers/linode/api.go @@ -221,20 +221,21 @@ type domainRecord struct { Port uint16 `json:"port"` Service string `json:"service"` Protocol string `json:"protocol"` + Tag string `json:"tag"` TTLSec uint32 `json:"ttl_sec"` } type recordEditRequest struct { Type string `json:"type,omitempty"` - Name string `json:"name,omitempty"` - Target string `json:"target,omitempty"` + Name string `json:"name"` + Target string `json:"target"` Priority int `json:"priority,omitempty"` Weight int `json:"weight,omitempty"` Port int `json:"port,omitempty"` Service string `json:"service,omitempty"` Protocol string `json:"protocol,omitempty"` - // Documented as field `ttl` in the documentation, but in reality `ttl_sec` should be used - TTL int `json:"ttl_sec,omitempty"` + Tag string `json:"tag,omitempty"` + TTL int `json:"ttl_sec,omitempty"` } type errorResponse struct { diff --git a/providers/linode/linodeProvider.go b/providers/linode/linodeProvider.go index 9dcd0ea85..a7aad227b 100644 --- a/providers/linode/linodeProvider.go +++ b/providers/linode/linodeProvider.go @@ -86,7 +86,8 @@ func NewLinode(m map[string]string, metadata json.RawMessage) (providers.DNSServ } var features = providers.DocumentationNotes{ - providers.CanGetZones: providers.Unimplemented(), + providers.CanGetZones: providers.Can(), + providers.CanUseCAA: providers.Can("Linode doesn't support changing the CAA flag"), providers.DocDualHost: providers.Cannot(), providers.DocOfficiallySupported: providers.Cannot(), } @@ -107,10 +108,17 @@ func (api *linodeProvider) GetNameservers(domain string) ([]*models.Nameserver, // GetZoneRecords gets the records of a zone and returns them in RecordConfig format. func (api *linodeProvider) GetZoneRecords(domain string) (models.Records, error) { - return nil, fmt.Errorf("not implemented") - // This enables the get-zones subcommand. - // Implement this by extracting the code from GetDomainCorrections into - // a single function. For most providers this should be relatively easy. + if api.domainIndex == nil { + if err := api.fetchDomainList(); err != nil { + return nil, err + } + } + domainID, ok := api.domainIndex[domain] + if !ok { + return nil, fmt.Errorf("'%s' not a zone in Linode account", domain) + } + + return api.getRecordsForDomain(domainID, domain) } // GetDomainCorrections returns the corrections for a domain. @@ -132,29 +140,11 @@ func (api *linodeProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mod return nil, fmt.Errorf("'%s' not a zone in Linode account", dc.Name) } - records, err := api.getRecords(domainID) + existingRecords, err := api.getRecordsForDomain(domainID, dc.Name) if err != nil { return nil, err } - existingRecords := make([]*models.RecordConfig, len(records), len(records)+len(defaultNameServerNames)) - for i := range records { - existingRecords[i] = toRc(dc, &records[i]) - } - - // Linode always has read-only NS servers, but these are not mentioned in the API response - // https://github.com/linode/manager/blob/edd99dc4e1be5ab8190f243c3dbf8b830716255e/src/constants.js#L184 - for _, name := range defaultNameServerNames { - rc := &models.RecordConfig{ - Type: "NS", - Original: &domainRecord{}, - } - rc.SetLabelFromFQDN(dc.Name, dc.Name) - rc.SetTarget(name) - - existingRecords = append(existingRecords, rc) - } - // Normalize models.PostProcessRecords(existingRecords) @@ -235,25 +225,54 @@ func (api *linodeProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mod return corrections, nil } -func toRc(dc *models.DomainConfig, r *domainRecord) *models.RecordConfig { +func (api *linodeProvider) getRecordsForDomain(domainID int, domain string) (models.Records, error) { + records, err := api.getRecords(domainID) + if err != nil { + return nil, err + } + + existingRecords := make([]*models.RecordConfig, len(records), len(records)+len(defaultNameServerNames)) + for i := range records { + existingRecords[i] = toRc(domain, &records[i]) + } + + // Linode always has read-only NS servers, but these are not mentioned in the API response + // https://github.com/linode/manager/blob/edd99dc4e1be5ab8190f243c3dbf8b830716255e/src/constants.js#L184 + for _, name := range defaultNameServerNames { + rc := &models.RecordConfig{ + Type: "NS", + Original: &domainRecord{}, + } + rc.SetLabelFromFQDN(domain, domain) + rc.SetTarget(name) + + existingRecords = append(existingRecords, rc) + } + + return existingRecords, nil +} + +func toRc(domain string, r *domainRecord) *models.RecordConfig { rc := &models.RecordConfig{ Type: r.Type, TTL: r.TTLSec, MxPreference: r.Priority, SrvPriority: r.Priority, SrvWeight: r.Weight, - SrvPort: uint16(r.Port), + SrvPort: r.Port, + CaaTag: r.Tag, Original: r, } - rc.SetLabel(r.Name, dc.Name) + rc.SetLabel(r.Name, domain) switch rtype := r.Type; rtype { // #rtype_variations - case "TXT": - rc.SetTargetTXT(r.Target) case "CNAME", "MX", "NS", "SRV": - rc.SetTarget(dnsutil.AddOrigin(r.Target+".", dc.Name)) - default: + rc.SetTarget(dnsutil.AddOrigin(r.Target+".", domain)) + case "CAA": + // Linode doesn't support CAA flags and just returns the tag and value separately rc.SetTarget(r.Target) + default: + rc.PopulateFromString(r.Type, r.Target, domain) } return rc @@ -277,11 +296,16 @@ func toReq(dc *models.DomainConfig, rc *models.RecordConfig) (*recordEditRequest // Linode uses the same property for MX and SRV priority switch rc.Type { // #rtype_variations - case "A", "AAAA", "NS", "PTR", "TXT", "SOA", "TLSA", "CAA": + case "A", "AAAA", "NS", "PTR", "TXT", "SOA", "TLSA": // Nothing special. case "MX": req.Priority = int(rc.MxPreference) req.Target = fixTarget(req.Target, dc.Name) + + // Linode doesn't use "." for a null MX record, it uses an empty name + if req.Target == "." { + req.Target = "" + } case "SRV": req.Priority = int(rc.SrvPriority) @@ -300,6 +324,8 @@ func toReq(dc *models.DomainConfig, rc *models.RecordConfig) (*recordEditRequest req.Name = "" case "CNAME": req.Target = fixTarget(req.Target, dc.Name) + case "CAA": + req.Tag = rc.CaaTag default: return nil, fmt.Errorf("linode.toReq rtype %q unimplemented", rc.Type) } @@ -329,5 +355,3 @@ func fixTTL(ttl uint32) uint32 { return allowedTTLValues[0] } - -// that have not been updated for a new RR type.