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:
@ -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'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>
|
||||||
|
@ -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 {
|
||||||
|
@ -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.
|
|
||||||
|
Reference in New Issue
Block a user