mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
DNSIMPLE: Fix TXT Handling, Second Edition (#1624)
* Fix typo and add sandbox information * Use SetTargetTXT in GetZoneRecords This fixes the behavior documented in #1622 Also apply cleanup to GetZoneRecords * Remove SetTargetTXT, does not work in all tests * Set The most correct TXT handling * Well, There's your problem * Add an audit and test for unpaired quotes * Add some commentary Co-authored-by: Tom Limoncelli <tlimoncelli@stackoverflow.com>
This commit is contained in:
@@ -7,16 +7,23 @@ jsId: DNSIMPLE
|
|||||||
# DNSimple Provider
|
# DNSimple Provider
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
To use this provider, add an entry to `creds.json` with `TYPE` set to `DIGITALOCEAN`
|
To use this provider, add an entry to `creds.json` with `TYPE` set to `DNSIMPLE`
|
||||||
along with a DNSimple account access token.
|
along with a DNSimple account access token.
|
||||||
|
|
||||||
Example:
|
You can also set the `baseurl` to use [DNSimple's free sandbox](https://developer.dnsimple.com/sandbox/) for testing.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dnsimple": {
|
"dnsimple": {
|
||||||
"TYPE": "DNSIMPLE",
|
"TYPE": "DNSIMPLE",
|
||||||
"token": "your-dnsimple-account-access-token"
|
"token": "your-dnsimple-account-access-token"
|
||||||
|
},
|
||||||
|
"dnsimple_sandbox": {
|
||||||
|
"TYPE": "DNSIMPLE",
|
||||||
|
"baseurl": "https://api.sandbox.dnsimple.com",
|
||||||
|
"token": "your-sandbox-account-access-token"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -845,6 +845,8 @@ func makeTests(t *testing.T) []*TestGroup {
|
|||||||
clear(),
|
clear(),
|
||||||
tc("Create TXT with double-quote", txt("foodq", `quo"te`)),
|
tc("Create TXT with double-quote", txt("foodq", `quo"te`)),
|
||||||
clear(),
|
clear(),
|
||||||
|
tc("Create TXT with double-quotes", txt("foodqs", `q"uo"te`)),
|
||||||
|
clear(),
|
||||||
tc("Create TXT with ws at end", txt("foows1", "with space at end ")),
|
tc("Create TXT with ws at end", txt("foows1", "with space at end ")),
|
||||||
//clear(),
|
//clear(),
|
||||||
// TODO(tlim): Re-add this when we fix the RFC1035 escaped-quotes issue.
|
// TODO(tlim): Re-add this when we fix the RFC1035 escaped-quotes issue.
|
||||||
|
|||||||
@@ -141,3 +141,19 @@ func TxtNotEmpty(records []*models.RecordConfig) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TxtNoUnpairedDoubleQuotes audits TXT records for strings that contain unpaired doublequotes.
|
||||||
|
func TxtNoUnpairedDoubleQuotes(records []*models.RecordConfig) error {
|
||||||
|
for _, rc := range records {
|
||||||
|
|
||||||
|
if rc.HasFormatIdenticalToTXT() {
|
||||||
|
for _, txt := range rc.TxtStrings {
|
||||||
|
if strings.Count(txt, `"`)%2 == 1 {
|
||||||
|
return fmt.Errorf("txtstring contains unpaired doublequotes")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,8 +8,22 @@ import (
|
|||||||
// AuditRecords returns an error if any records are not
|
// AuditRecords returns an error if any records are not
|
||||||
// supportable by this provider.
|
// supportable by this provider.
|
||||||
func AuditRecords(records []*models.RecordConfig) error {
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
if err := recordaudit.TxtNoDoubleQuotes(records); err != nil {
|
//TODO(onlyhavecans) I think we can support multiple strings.
|
||||||
|
if err := recordaudit.TxtNoMultipleStrings(records); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := recordaudit.TxtNoTrailingSpace(records); err != nil {
|
||||||
|
return err
|
||||||
|
} // as of 2022-07
|
||||||
|
|
||||||
|
if err := recordaudit.TxtNotEmpty(records); err != nil {
|
||||||
|
return err
|
||||||
|
} // as of 2022-07
|
||||||
|
|
||||||
|
if err := recordaudit.TxtNoUnpairedDoubleQuotes(records); err != nil {
|
||||||
|
return err
|
||||||
|
} // as of 2022-07
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/StackExchange/dnscontrol/v3/pkg/printer"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -14,7 +13,7 @@ import (
|
|||||||
|
|
||||||
"github.com/StackExchange/dnscontrol/v3/models"
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
||||||
"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
|
"github.com/StackExchange/dnscontrol/v3/pkg/printer"
|
||||||
"github.com/StackExchange/dnscontrol/v3/providers"
|
"github.com/StackExchange/dnscontrol/v3/providers"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -77,51 +76,54 @@ func (c *dnsimpleProvider) GetZoneRecords(domain string) (models.Records, error)
|
|||||||
if r.Type == "SOA" {
|
if r.Type == "SOA" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Name == "" {
|
if r.Name == "" {
|
||||||
r.Name = "@"
|
r.Name = "@"
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Type == "CNAME" || r.Type == "MX" || r.Type == "ALIAS" || r.Type == "NS" {
|
if r.Type == "CNAME" || r.Type == "MX" || r.Type == "ALIAS" || r.Type == "NS" {
|
||||||
r.Content += "."
|
r.Content += "."
|
||||||
}
|
}
|
||||||
|
|
||||||
// DNSimple adds TXT records that mirror the alias records.
|
// DNSimple adds TXT records that mirror the alias records.
|
||||||
// They manage them on ALIAS updates, so pretend they don't exist
|
// They manage them on ALIAS updates, so pretend they don't exist
|
||||||
if r.Type == "TXT" && strings.HasPrefix(r.Content, "ALIAS for ") {
|
if r.Type == "TXT" && strings.HasPrefix(r.Content, "ALIAS for ") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
rec := &models.RecordConfig{
|
rec := &models.RecordConfig{
|
||||||
TTL: uint32(r.TTL),
|
TTL: uint32(r.TTL),
|
||||||
Original: r,
|
Original: r,
|
||||||
}
|
}
|
||||||
rec.SetLabel(r.Name, domain)
|
rec.SetLabel(r.Name, domain)
|
||||||
|
|
||||||
|
var err error
|
||||||
switch rtype := r.Type; rtype {
|
switch rtype := r.Type; rtype {
|
||||||
case "DNSKEY", "CDNSKEY", "CDS":
|
case "DNSKEY", "CDNSKEY", "CDS":
|
||||||
continue
|
continue
|
||||||
case "ALIAS", "URL":
|
case "ALIAS", "URL":
|
||||||
rec.Type = r.Type
|
rec.Type = r.Type
|
||||||
if err := rec.SetTarget(r.Content); err != nil {
|
err = rec.SetTarget(r.Content)
|
||||||
return nil, fmt.Errorf("unparsable record received from dnsimple: %w", err)
|
|
||||||
}
|
|
||||||
case "DS":
|
case "DS":
|
||||||
if err := rec.SetTargetDSString(r.Content); err != nil {
|
err = rec.SetTargetDSString(r.Content)
|
||||||
return nil, fmt.Errorf("unparsable record received from dnsimple: %w", err)
|
|
||||||
}
|
|
||||||
case "MX":
|
case "MX":
|
||||||
if err := rec.SetTargetMX(uint16(r.Priority), r.Content); err != nil {
|
err = rec.SetTargetMX(uint16(r.Priority), r.Content)
|
||||||
return nil, fmt.Errorf("unparsable record received from dnsimple: %w", err)
|
|
||||||
}
|
|
||||||
case "SRV":
|
case "SRV":
|
||||||
parts := strings.Fields(r.Content)
|
parts := strings.Fields(r.Content)
|
||||||
if len(parts) == 3 {
|
if len(parts) == 3 {
|
||||||
r.Content += "."
|
r.Content += "."
|
||||||
}
|
}
|
||||||
if err := rec.SetTargetSRVPriorityString(uint16(r.Priority), r.Content); err != nil {
|
err = rec.SetTargetSRVPriorityString(uint16(r.Priority), r.Content)
|
||||||
return nil, fmt.Errorf("unparsable record received from dnsimple: %w", err)
|
case "TXT":
|
||||||
}
|
err = rec.SetTargetTXT(r.Content)
|
||||||
default:
|
default:
|
||||||
if err := rec.PopulateFromString(r.Type, r.Content, domain); err != nil {
|
err = rec.PopulateFromString(r.Type, r.Content, domain)
|
||||||
return nil, fmt.Errorf("unparsable record received from dnsimple: %w", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unparsable record received from dnsimple: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
cleanedRecords = append(cleanedRecords, rec)
|
cleanedRecords = append(cleanedRecords, rec)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,7 +154,6 @@ func (c *dnsimpleProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mod
|
|||||||
|
|
||||||
// Normalize
|
// Normalize
|
||||||
models.PostProcessRecords(actual)
|
models.PostProcessRecords(actual)
|
||||||
txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
|
|
||||||
|
|
||||||
differ := diff.New(dc)
|
differ := diff.New(dc)
|
||||||
_, create, del, modify, err := differ.IncrementalDiff(actual)
|
_, create, del, modify, err := differ.IncrementalDiff(actual)
|
||||||
@@ -592,7 +593,7 @@ func getTargetRecordContent(rc *models.RecordConfig) string {
|
|||||||
case "SRV":
|
case "SRV":
|
||||||
return fmt.Sprintf("%d %d %s", rc.SrvWeight, rc.SrvPort, rc.GetTargetField())
|
return fmt.Sprintf("%d %d %s", rc.SrvWeight, rc.SrvPort, rc.GetTargetField())
|
||||||
case "TXT":
|
case "TXT":
|
||||||
return rc.GetTargetRFC1035Quoted()
|
return rc.GetTargetTXTJoined()
|
||||||
default:
|
default:
|
||||||
return rc.GetTargetField()
|
return rc.GetTargetField()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user