1
0
mirror of https://github.com/StackExchange/dnscontrol.git synced 2024-05-11 05:55:12 +00:00

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 <tlimoncelli@stackoverflow.com>
This commit is contained in:
Koen Vlaswinkel
2022-03-25 20:22:20 +01:00
committed by GitHub
parent ffe21c6e6d
commit 02d76affc0
3 changed files with 68 additions and 41 deletions

View File

@ -627,7 +627,9 @@
<td class="success"> <td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i> <i class="fa fa-check text-success" aria-hidden="true"></i>
</td> </td>
<td><i class="fa fa-minus dim"></i></td> <td class="success" data-toggle="tooltip" data-container="body" data-placement="top" title="Linode doesn&#39;t support changing the CAA flag">
<i class="fa has-tooltip fa-check text-success" aria-hidden="true"></i>
</td>
<td class="danger"> <td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></i> <i class="fa fa-times text-danger" aria-hidden="true"></i>
</td> </td>
@ -1819,8 +1821,8 @@
<td class="success"> <td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i> <i class="fa fa-check text-success" aria-hidden="true"></i>
</td> </td>
<td class="info"> <td class="success">
<i class="fa fa-circle-o text-info" aria-hidden="true"></i> <i class="fa fa-check text-success" aria-hidden="true"></i>
</td> </td>
<td class="success"> <td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i> <i class="fa fa-check text-success" aria-hidden="true"></i>

View File

@ -221,20 +221,21 @@ type domainRecord struct {
Port uint16 `json:"port"` Port uint16 `json:"port"`
Service string `json:"service"` Service string `json:"service"`
Protocol string `json:"protocol"` Protocol string `json:"protocol"`
Tag string `json:"tag"`
TTLSec uint32 `json:"ttl_sec"` TTLSec uint32 `json:"ttl_sec"`
} }
type recordEditRequest struct { type recordEditRequest struct {
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
Name string `json:"name,omitempty"` Name string `json:"name"`
Target string `json:"target,omitempty"` Target string `json:"target"`
Priority int `json:"priority,omitempty"` Priority int `json:"priority,omitempty"`
Weight int `json:"weight,omitempty"` Weight int `json:"weight,omitempty"`
Port int `json:"port,omitempty"` Port int `json:"port,omitempty"`
Service string `json:"service,omitempty"` Service string `json:"service,omitempty"`
Protocol string `json:"protocol,omitempty"` Protocol string `json:"protocol,omitempty"`
// Documented as field `ttl` in the documentation, but in reality `ttl_sec` should be used Tag string `json:"tag,omitempty"`
TTL int `json:"ttl_sec,omitempty"` TTL int `json:"ttl_sec,omitempty"`
} }
type errorResponse struct { type errorResponse struct {

View File

@ -86,7 +86,8 @@ func NewLinode(m map[string]string, metadata json.RawMessage) (providers.DNSServ
} }
var features = providers.DocumentationNotes{ 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.DocDualHost: providers.Cannot(),
providers.DocOfficiallySupported: 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. // GetZoneRecords gets the records of a zone and returns them in RecordConfig format.
func (api *linodeProvider) GetZoneRecords(domain string) (models.Records, error) { func (api *linodeProvider) GetZoneRecords(domain string) (models.Records, error) {
return nil, fmt.Errorf("not implemented") if api.domainIndex == nil {
// This enables the get-zones subcommand. if err := api.fetchDomainList(); err != nil {
// Implement this by extracting the code from GetDomainCorrections into return nil, err
// a single function. For most providers this should be relatively easy. }
}
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. // 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) 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 { if err != nil {
return nil, err 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 // Normalize
models.PostProcessRecords(existingRecords) models.PostProcessRecords(existingRecords)
@ -235,25 +225,54 @@ func (api *linodeProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mod
return corrections, nil 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{ rc := &models.RecordConfig{
Type: r.Type, Type: r.Type,
TTL: r.TTLSec, TTL: r.TTLSec,
MxPreference: r.Priority, MxPreference: r.Priority,
SrvPriority: r.Priority, SrvPriority: r.Priority,
SrvWeight: r.Weight, SrvWeight: r.Weight,
SrvPort: uint16(r.Port), SrvPort: r.Port,
CaaTag: r.Tag,
Original: r, Original: r,
} }
rc.SetLabel(r.Name, dc.Name) rc.SetLabel(r.Name, domain)
switch rtype := r.Type; rtype { // #rtype_variations switch rtype := r.Type; rtype { // #rtype_variations
case "TXT":
rc.SetTargetTXT(r.Target)
case "CNAME", "MX", "NS", "SRV": case "CNAME", "MX", "NS", "SRV":
rc.SetTarget(dnsutil.AddOrigin(r.Target+".", dc.Name)) rc.SetTarget(dnsutil.AddOrigin(r.Target+".", domain))
default: case "CAA":
// Linode doesn't support CAA flags and just returns the tag and value separately
rc.SetTarget(r.Target) rc.SetTarget(r.Target)
default:
rc.PopulateFromString(r.Type, r.Target, domain)
} }
return rc 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 // Linode uses the same property for MX and SRV priority
switch rc.Type { // #rtype_variations switch rc.Type { // #rtype_variations
case "A", "AAAA", "NS", "PTR", "TXT", "SOA", "TLSA", "CAA": case "A", "AAAA", "NS", "PTR", "TXT", "SOA", "TLSA":
// Nothing special. // Nothing special.
case "MX": case "MX":
req.Priority = int(rc.MxPreference) req.Priority = int(rc.MxPreference)
req.Target = fixTarget(req.Target, dc.Name) 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": case "SRV":
req.Priority = int(rc.SrvPriority) req.Priority = int(rc.SrvPriority)
@ -300,6 +324,8 @@ func toReq(dc *models.DomainConfig, rc *models.RecordConfig) (*recordEditRequest
req.Name = "" req.Name = ""
case "CNAME": case "CNAME":
req.Target = fixTarget(req.Target, dc.Name) req.Target = fixTarget(req.Target, dc.Name)
case "CAA":
req.Tag = rc.CaaTag
default: default:
return nil, fmt.Errorf("linode.toReq rtype %q unimplemented", rc.Type) return nil, fmt.Errorf("linode.toReq rtype %q unimplemented", rc.Type)
} }
@ -329,5 +355,3 @@ func fixTTL(ttl uint32) uint32 {
return allowedTTLValues[0] return allowedTTLValues[0]
} }
// that have not been updated for a new RR type.