mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2025-12-09 13:46:07 +08:00
BUGFIX: IDN support is broken for domain names (#3845)
# Issue Fixes https://github.com/StackExchange/dnscontrol/issues/3842 CC @das7pad # Resolution Convert domain.Name to IDN earlier in the pipeline. Hack the --domains processing to convert everything to IDN. * Domain names are now stored 3 ways: The original input from dnsconfig.js, canonical IDN format (`xn--...`), and Unicode format. All are downcased. Providers that haven't been updated will receive the IDN format instead of the original input format. This might break some providers but only for users with unicode in their D("domain.tld"). PLEASE TEST YOUR PROVIDER. * BIND filename formatting options have been added to access the new formats. # Breaking changes * BIND zonefiles may change. The default used the name input in the D() statement. It now defaults to the IDN name + "!tag" if there is a tag. * Providers that are not IDN-aware may break (hopefully only if they weren't processing IDN already) --------- Co-authored-by: Jakob Ackermann <das7pad@outlook.com>
This commit is contained in:
parent
9aad2926fb
commit
1b2f5d4d34
83 changed files with 623 additions and 470 deletions
|
|
@ -303,39 +303,3 @@ func (args *FilterArgs) flags() []cli.Flag {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
// domainInList takes a domain and a list of domains and returns true if the
|
||||
// domain is in the list, accounting for wildcards and tags.
|
||||
func domainInList(domain string, list []string) bool {
|
||||
for _, item := range list {
|
||||
if item == domain {
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(item, "*") && strings.HasSuffix(domain, item[1:]) {
|
||||
return true
|
||||
}
|
||||
filterDom, filterTag, isFilterTagged := strings.Cut(item, "!")
|
||||
splitDom, domainTag, isDomainTagged := strings.Cut(domain, "!")
|
||||
if splitDom == filterDom {
|
||||
if isDomainTagged {
|
||||
if filterTag == "*" {
|
||||
return true
|
||||
}
|
||||
if domainTag == "" && !isFilterTagged {
|
||||
// domain example.com! == filter example.com
|
||||
return true
|
||||
}
|
||||
if isFilterTagged && domainTag == filterTag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if isFilterTagged {
|
||||
if filterTag == "" && !isDomainTagged {
|
||||
// filter example.com! == domain example.com
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,127 +0,0 @@
|
|||
package commands
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test_domainInList(t *testing.T) {
|
||||
type args struct {
|
||||
domain string
|
||||
list []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "small",
|
||||
args: args{
|
||||
domain: "foo.com",
|
||||
list: []string{"foo.com"},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "big",
|
||||
args: args{
|
||||
domain: "foo.com",
|
||||
list: []string{"example.com", "foo.com", "baz.com"},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "missing",
|
||||
args: args{
|
||||
domain: "foo.com",
|
||||
list: []string{"bar.com"},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "wildcard",
|
||||
args: args{
|
||||
domain: "*.10.in-addr.arpa",
|
||||
list: []string{"bar.com", "10.in-addr.arpa", "example.com"},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "wildcardmissing",
|
||||
args: args{
|
||||
domain: "*.10.in-addr.arpa",
|
||||
list: []string{"bar.com", "6.in-addr.arpa", "example.com"},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "tagged",
|
||||
args: args{
|
||||
domain: "foo.com!bar",
|
||||
list: []string{"foo.com"},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "taggedWildcard",
|
||||
args: args{
|
||||
domain: "foo.com!bar",
|
||||
list: []string{"foo.com!*"},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "taggedWildcardMatchesEmpty",
|
||||
args: args{
|
||||
domain: "foo.com!",
|
||||
list: []string{"foo.com!*"},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "taggedWildcardNotMatchUntagged",
|
||||
args: args{
|
||||
domain: "foo.com",
|
||||
list: []string{"foo.com!*"},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "taggedEmtpy",
|
||||
args: args{
|
||||
domain: "foo.com",
|
||||
list: []string{"foo.com!"},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "domainTaggedEmtpy",
|
||||
args: args{
|
||||
domain: "foo.com!",
|
||||
list: []string{"foo.com"},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "filterTaggedNoMatch",
|
||||
args: args{
|
||||
domain: "foo.com",
|
||||
list: []string{"foo.com!foo"},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "domainTaggedNoMatch",
|
||||
args: args{
|
||||
domain: "foo.com!foo",
|
||||
list: []string{"foo.com"},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := domainInList(tt.args.domain, tt.args.list); got != tt.want {
|
||||
t.Errorf("domainInList() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -200,7 +200,10 @@ func GetZone(args GetZoneArgs) error {
|
|||
// fetch all of the records
|
||||
zoneRecs := make([]models.Records, len(zones))
|
||||
for i, zone := range zones {
|
||||
recs, err := provider.GetZoneRecords(zone, nil)
|
||||
recs, err := provider.GetZoneRecords(zone,
|
||||
map[string]string{
|
||||
models.DomainUniqueName: zone,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed GetZone gzr: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ func testFormat(t *testing.T, domain, format string) {
|
|||
// Read the expected result
|
||||
want, err := os.ReadFile(expectedFilename)
|
||||
if err != nil {
|
||||
log.Fatal(fmt.Errorf("can't read expected %q: %w", outfile.Name(), err))
|
||||
log.Fatal(fmt.Errorf("can't read expected %q: %w", expectedFilename, err))
|
||||
}
|
||||
|
||||
if w, g := string(want), string(got); w != g {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
"github.com/StackExchange/dnscontrol/v4/models"
|
||||
"github.com/StackExchange/dnscontrol/v4/pkg/bindserial"
|
||||
"github.com/StackExchange/dnscontrol/v4/pkg/credsfile"
|
||||
"github.com/StackExchange/dnscontrol/v4/pkg/domaintags"
|
||||
"github.com/StackExchange/dnscontrol/v4/pkg/nameservers"
|
||||
"github.com/StackExchange/dnscontrol/v4/pkg/normalize"
|
||||
"github.com/StackExchange/dnscontrol/v4/pkg/notifications"
|
||||
|
|
@ -288,7 +289,7 @@ func prun(args PPreviewArgs, push bool, interactive bool, out printer.CLI, repor
|
|||
continue // Do not emit noise when zone exists
|
||||
}
|
||||
if !started {
|
||||
out.StartDomain(zone.GetUniqueName())
|
||||
out.StartDomain(zone)
|
||||
started = true
|
||||
}
|
||||
skip := skipProvider(provider.Name, providersToProcess)
|
||||
|
|
@ -351,7 +352,7 @@ func prun(args PPreviewArgs, push bool, interactive bool, out printer.CLI, repor
|
|||
// Now we know what to do, print or do the tasks.
|
||||
out.PrintfIf(fullMode, "PHASE 3: CORRECTIONS\n")
|
||||
for _, zone := range zonesToProcess {
|
||||
out.StartDomain(zone.GetUniqueName())
|
||||
out.StartDomain(zone)
|
||||
|
||||
// Process DNS provider changes:
|
||||
providersToProcess := whichProvidersToProcess(zone.DNSProviderInstances, args.Providers)
|
||||
|
|
@ -400,29 +401,16 @@ func prun(args PPreviewArgs, push bool, interactive bool, out printer.CLI, repor
|
|||
return nil
|
||||
}
|
||||
|
||||
// func countActions(corrections []*models.Correction) int {
|
||||
// r := 0
|
||||
// for _, c := range corrections {
|
||||
// if c.F != nil {
|
||||
// r++
|
||||
// }
|
||||
// }
|
||||
// return r
|
||||
//}
|
||||
|
||||
// whichZonesToProcess takes a list of DomainConfigs and a filter string and
|
||||
// returns a list of DomainConfigs whose metadata[DomainUniqueName] matched the
|
||||
// returns a list of DomainConfigs whose Domain.UniqueName matched the
|
||||
// filter. The filter string is a comma-separated list of domain names. If the
|
||||
// filter string is empty or "all", all domains are returned.
|
||||
func whichZonesToProcess(domains []*models.DomainConfig, filter string) []*models.DomainConfig {
|
||||
if filter == "" || filter == "all" {
|
||||
return domains
|
||||
}
|
||||
fh := domaintags.CompilePermitList(filter)
|
||||
|
||||
permitList := strings.Split(filter, ",")
|
||||
var picked []*models.DomainConfig
|
||||
for _, domain := range domains {
|
||||
if domainInList(domain.GetUniqueName(), permitList) {
|
||||
if fh.Permitted(domain.GetUniqueName()) {
|
||||
picked = append(picked, domain)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ func Test_whichZonesToProcess(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, dc := range allDC {
|
||||
dc.UpdateSplitHorizonNames()
|
||||
dc.PostProcess()
|
||||
}
|
||||
|
||||
type args struct {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ Example:
|
|||
{
|
||||
"bind": {
|
||||
"TYPE": "BIND",
|
||||
"directory": "myzones"
|
||||
"directory": "myzones",
|
||||
"filenameformat": "%U.zone"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -89,10 +90,13 @@ file name is the name as specified in the `D()` function plus ".zone".
|
|||
|
||||
The filenameformat is a string with a few printf-like `%` verbs:
|
||||
|
||||
* `%U` the domain name as specified in `D()`
|
||||
* `%D` the domain name without any split horizon tag (the "example.com" part of "example.com!tag")
|
||||
* `%T` the split horizon tag, or "" (the "tag" part of "example.com!tag")
|
||||
* `%?x` this returns `x` if the split horizon tag is non-null, otherwise nothing. `x` can be any printable.
|
||||
* The domain name without tag (the `example.com` part of `example.com!tag`):
|
||||
* `%D` as specified in `D()` (no IDN conversion, but downcased)
|
||||
* `%I` converted to IDN/Punycode (`xn--...`) and downcased.
|
||||
* `%N` converted to Unicode (downcased first)
|
||||
* `%T` the split horizon tag, or "" (the `tag` part of `example.com!tag`)
|
||||
* `%?x` this returns `x` if the split horizon tag is non-null, otherwise nothing. `x` can be any printable but is usually `!`.
|
||||
* `%U` short for "%I%?!%T". This is the universal, canonical, name for the domain used for comparisons within DNSControl. This is best for filenames which is why it is used in the default.
|
||||
* `%%` `%`
|
||||
* ordinary characters (not `%`) are copied unchanged to the output stream
|
||||
* FYI: format strings must not end with an incomplete `%` or `%?`
|
||||
|
|
@ -101,19 +105,17 @@ Typical values:
|
|||
|
||||
* `%U.zone` (The default)
|
||||
* `example.com.zone` or `example.com!tag.zone`
|
||||
* `%T%*U%D.zone` (optional tag and `_` + domain + `.zone`)
|
||||
* `%T%?_%I.zone` (optional tag and `_` + domain + `.zone`)
|
||||
* `tag_example.com.zone` or `example.com.zone`
|
||||
* `db_%T%?_%D`
|
||||
* `db_inside_example.com` or `db_example.com`
|
||||
* `db_%D`
|
||||
* `db_example.com`
|
||||
|
||||
The last example will generate the same name for both
|
||||
`D("example.com!inside")` and `D("example.com!outside")`. This
|
||||
assumes two BIND providers are configured in `creds.json`, each with
|
||||
a different `directory` setting. Otherwise `dnscontrol` will write
|
||||
both domains to the same file, flapping between the two back and
|
||||
forth.
|
||||
{% hint style="warning" %}
|
||||
**Warning** DNSControl will not warn you if two zones generate the same
|
||||
filename. Instead, each will write to the same place. The content would end up
|
||||
flapping back and forth between the two. The best way to prevent this is to
|
||||
always include the tag (`%T`) or use `%U` which includes the tag.
|
||||
{% endhint %}
|
||||
|
||||
(new in v4.2.0) `dnscontrol push` will create subdirectories along the path to
|
||||
the filename. This includes both the portion of the path created by the
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ func getDomainConfigWithNameservers(t *testing.T, prv providers.DNSServiceProvid
|
|||
dc := &models.DomainConfig{
|
||||
Name: domainName,
|
||||
}
|
||||
dc.UpdateSplitHorizonNames()
|
||||
dc.PostProcess()
|
||||
|
||||
// fix up nameservers
|
||||
ns, err := prv.GetNameservers(domainName)
|
||||
|
|
@ -148,6 +148,8 @@ func makeChanges(t *testing.T, prv providers.DNSServiceProvider, dc *models.Doma
|
|||
return
|
||||
}
|
||||
|
||||
//fmt.Printf("DEBUG: Running test %q: Names %q %q %q\n", desc, dom.Name, dom.NameRaw, dom.NameUnicode)
|
||||
|
||||
// get and run corrections for first time
|
||||
_, corrections, actualChangeCount, err := zonerecs.CorrectZoneRecords(prv, dom)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -105,17 +105,10 @@ type Correction struct {
|
|||
Msg string
|
||||
}
|
||||
|
||||
// DomainContainingFQDN finds the best domain from the dns config for the given record fqdn.
|
||||
// It will chose the domain whose name is the longest suffix match for the fqdn.
|
||||
func (config *DNSConfig) DomainContainingFQDN(fqdn string) *DomainConfig {
|
||||
fqdn = strings.TrimSuffix(fqdn, ".")
|
||||
longestLength := 0
|
||||
var d *DomainConfig
|
||||
for _, dom := range config.Domains {
|
||||
if (dom.Name == fqdn || strings.HasSuffix(fqdn, "."+dom.Name)) && len(dom.Name) > longestLength {
|
||||
longestLength = len(dom.Name)
|
||||
d = dom
|
||||
}
|
||||
// PostProcess performs and post-processing required after running dnsconfig.js and loading the result.
|
||||
func (config *DNSConfig) PostProcess() error {
|
||||
for _, domain := range config.Domains {
|
||||
domain.PostProcess()
|
||||
}
|
||||
return d
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,28 +2,33 @@ package models
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/v4/pkg/domaintags"
|
||||
"github.com/qdm12/reprint"
|
||||
"golang.org/x/net/idna"
|
||||
)
|
||||
|
||||
const (
|
||||
// DomainUniqueName is the full `example.com!tag` name`
|
||||
DomainUniqueName = "dnscontrol_uniquename"
|
||||
// DomainTag is the tag part of `example.com!tag` name
|
||||
DomainTag = "dnscontrol_tag"
|
||||
DomainTag = "dnscontrol_tag" // A copy of DomainConfig.Tag
|
||||
DomainUniqueName = "dnscontrol_uniquename" // A copy of DomainConfig.UniqueName
|
||||
DomainNameRaw = "dnscontrol_nameraw" // A copy of DomainConfig.NameRaw
|
||||
DomainNameIDN = "dnscontrol_nameidn" // A copy of DomainConfig.NameIDN
|
||||
DomainNameUnicode = "dnscontrol_nameunicode" // A copy of DomainConfig.NameUnicode
|
||||
)
|
||||
|
||||
// DomainConfig describes a DNS domain (technically a DNS zone).
|
||||
type DomainConfig struct {
|
||||
Name string `json:"name"` // NO trailing "."
|
||||
Name string `json:"name"` // NO trailing "." Converted to IDN (punycode) early in the pipeline.
|
||||
NameRaw string `json:"-"` // name as entered by user in dnsconfig.js
|
||||
NameUnicode string `json:"-"` // name in Unicode format
|
||||
|
||||
Tag string `json:"tag,omitempty"` // Split horizon tag.
|
||||
UniqueName string `json:"-"` // .Name + "!" + .Tag
|
||||
|
||||
RegistrarName string `json:"registrar"`
|
||||
DNSProviderNames map[string]int `json:"dnsProviders"`
|
||||
|
||||
// Metadata[DomainUniqueName] // .Name + "!" + .Tag
|
||||
// Metadata[DomainTag] // split horizon tag
|
||||
Metadata map[string]string `json:"meta,omitempty"`
|
||||
Records Records `json:"records"`
|
||||
Nameservers []*Nameserver `json:"nameservers,omitempty"`
|
||||
|
|
@ -56,43 +61,39 @@ type DomainConfig struct {
|
|||
pendingPopulateCorrections map[string][]*Correction // Corrections for zone creations at each provider
|
||||
}
|
||||
|
||||
// GetSplitHorizonNames returns the domain's name, uniquename, and tag.
|
||||
func (dc *DomainConfig) GetSplitHorizonNames() (name, uniquename, tag string) {
|
||||
return dc.Name, dc.Metadata[DomainUniqueName], dc.Metadata[DomainTag]
|
||||
}
|
||||
|
||||
// GetUniqueName returns the domain's uniquename.
|
||||
func (dc *DomainConfig) GetUniqueName() (uniquename string) {
|
||||
return dc.Metadata[DomainUniqueName]
|
||||
}
|
||||
|
||||
// UpdateSplitHorizonNames updates the split horizon fields
|
||||
// (uniquename and tag) based on name.
|
||||
func (dc *DomainConfig) UpdateSplitHorizonNames() {
|
||||
name, unique, tag := dc.GetSplitHorizonNames()
|
||||
|
||||
if unique == "" {
|
||||
unique = name
|
||||
}
|
||||
|
||||
if tag == "" {
|
||||
l := strings.SplitN(name, "!", 2)
|
||||
if len(l) == 2 {
|
||||
name = l[0]
|
||||
tag = l[1]
|
||||
}
|
||||
if tag == "" {
|
||||
// ensure empty tagged domain is treated as untagged
|
||||
unique = name
|
||||
}
|
||||
}
|
||||
|
||||
dc.Name = name
|
||||
// PostProcess performs and post-processing required after running dnsconfig.js and loading the result.
|
||||
// It is called by dns.go's PostProcess() function.
|
||||
func (dc *DomainConfig) PostProcess() {
|
||||
// Ensure the metadata map is initialized.
|
||||
if dc.Metadata == nil {
|
||||
dc.Metadata = map[string]string{}
|
||||
}
|
||||
dc.Metadata[DomainUniqueName] = unique
|
||||
dc.Metadata[DomainTag] = tag
|
||||
|
||||
// Turn the user-supplied name into the fixed forms.
|
||||
ff := domaintags.MakeDomainFixForms(dc.Name)
|
||||
dc.Tag, dc.NameRaw, dc.Name, dc.NameUnicode, dc.UniqueName = ff.Tag, ff.NameRaw, ff.NameIDN, ff.NameUnicode, ff.UniqueName
|
||||
|
||||
// Store the FixForms is Metadata so we don't have to change the signature of every function that might need them.
|
||||
// This is a bit ugly but avoids a huge refactor. Please avoid using these to make the future refactor easier.
|
||||
if dc.Tag != "" {
|
||||
dc.Metadata[DomainTag] = dc.Tag
|
||||
}
|
||||
//dc.Metadata[DomainNameRaw] = dc.NameRaw
|
||||
//dc.Metadata[DomainNameIDN] = dc.Name
|
||||
//dc.Metadata[DomainNameUnicode] = dc.NameUnicode
|
||||
dc.Metadata[DomainUniqueName] = dc.UniqueName
|
||||
}
|
||||
|
||||
// GetSplitHorizonNames returns the domain's name, uniquename, and tag.
|
||||
// Deprecated: use .Name, .Uniquename, and .Tag directly instead.
|
||||
func (dc *DomainConfig) GetSplitHorizonNames() (name, uniquename, tag string) {
|
||||
return dc.Name, dc.UniqueName, dc.Tag
|
||||
}
|
||||
|
||||
// GetUniqueName returns the domain's uniquename.
|
||||
// Deprecated: dc.UniqueName directly instead.
|
||||
func (dc *DomainConfig) GetUniqueName() (uniquename string) {
|
||||
return dc.UniqueName
|
||||
}
|
||||
|
||||
// Copy returns a deep copy of the DomainConfig.
|
||||
|
|
|
|||
|
|
@ -1,68 +0,0 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_UpdateSplitHorizonNames(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dc *DomainConfig
|
||||
expected *DomainConfig
|
||||
}{
|
||||
{
|
||||
name: "testNoTag",
|
||||
dc: &DomainConfig{
|
||||
Name: "example.com",
|
||||
},
|
||||
expected: &DomainConfig{
|
||||
Name: "example.com",
|
||||
Metadata: map[string]string{
|
||||
DomainUniqueName: "example.com",
|
||||
DomainTag: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "testEmptyTag",
|
||||
dc: &DomainConfig{
|
||||
Name: "example.com!",
|
||||
},
|
||||
expected: &DomainConfig{
|
||||
Name: "example.com",
|
||||
Metadata: map[string]string{
|
||||
DomainUniqueName: "example.com",
|
||||
DomainTag: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "testWithTag",
|
||||
dc: &DomainConfig{
|
||||
Name: "example.com!john",
|
||||
},
|
||||
expected: &DomainConfig{
|
||||
Name: "example.com",
|
||||
Metadata: map[string]string{
|
||||
DomainUniqueName: "example.com!john",
|
||||
DomainTag: "john",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.dc.UpdateSplitHorizonNames()
|
||||
if tt.dc.Name != tt.expected.Name {
|
||||
t.Errorf("expected name %s, got %s", tt.expected.Name, tt.dc.Name)
|
||||
}
|
||||
if tt.dc.Metadata[DomainUniqueName] != tt.expected.Metadata[DomainUniqueName] {
|
||||
t.Errorf("expected unique name %s, got %s", tt.expected.Metadata[DomainUniqueName], tt.dc.Metadata[DomainUniqueName])
|
||||
}
|
||||
if tt.dc.Metadata[DomainTag] != tt.expected.Metadata[DomainTag] {
|
||||
t.Errorf("expected tag %s, got %s", tt.expected.Metadata[DomainTag], tt.dc.Metadata[DomainTag])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@ func (rc *RecordConfig) SetTargetCAA(flag uint8, tag string, target string) erro
|
|||
// Per: https://www.iana.org/assignments/pkix-parameters/pkix-parameters.xhtml#caa-properties excluding reserved tags
|
||||
allowedTags := []string{"issue", "issuewild", "iodef", "contactemail", "contactphone", "issuemail", "issuevmc"}
|
||||
if !slices.Contains(allowedTags, tag) {
|
||||
return fmt.Errorf("CAA tag (%v) is not one of the valid types.", tag)
|
||||
return fmt.Errorf("CAA tag (%v) is not one of the valid types", tag)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
81
pkg/domaintags/domaintags.go
Normal file
81
pkg/domaintags/domaintags.go
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
package domaintags
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/idna"
|
||||
)
|
||||
|
||||
// DomainFixedForms stores the various fixed forms of a domain name and tag.
|
||||
type DomainFixedForms struct {
|
||||
NameRaw string // "originalinput.com" (name as input by the user, lowercased (no tag))
|
||||
NameIDN string // "punycode.com"
|
||||
NameUnicode string // "unicode.com" (converted to downcase BEFORE unicode conversion)
|
||||
UniqueName string // "punycode.com!tag"
|
||||
|
||||
Tag string // The tag portion of `example.com!tag`
|
||||
HasBang bool // Was there a "!" in the input when creating this struct?
|
||||
}
|
||||
|
||||
// MakeDomainFixedForms turns the user-supplied name into the fixed forms.
|
||||
// * .Tag: the domain tag (of "example.com!tag")
|
||||
// * .NameRaw: lowercase version of how the user input the name in dnsconfig.js.
|
||||
// * .Name: punycode version, downcased.
|
||||
// * .NameUnicode: unicode version of the name, downcased.
|
||||
// * .UniqueName: "example.com!tag" unique across the entire config.
|
||||
func MakeDomainFixForms(n string) DomainFixedForms {
|
||||
var err error
|
||||
var tag, nameRaw, nameIDN, nameUnicode, uniqueName string
|
||||
var hasBang bool
|
||||
|
||||
// Split tag from name.
|
||||
p := strings.SplitN(n, "!", 2)
|
||||
if len(p) == 2 {
|
||||
tag = p[1]
|
||||
hasBang = true
|
||||
} else {
|
||||
tag = ""
|
||||
hasBang = false
|
||||
}
|
||||
|
||||
nameRaw = strings.ToLower(p[0])
|
||||
if strings.HasPrefix(n, nameRaw) {
|
||||
// Avoid pointless duplication.
|
||||
nameRaw = n[0:len(nameRaw)]
|
||||
}
|
||||
|
||||
nameIDN, err = idna.ToASCII(nameRaw)
|
||||
if err != nil {
|
||||
nameIDN = nameRaw // Fallback to raw name on error.
|
||||
} else {
|
||||
// Avoid pointless duplication.
|
||||
if nameIDN == nameRaw {
|
||||
nameIDN = nameRaw
|
||||
}
|
||||
}
|
||||
|
||||
nameUnicode, err = idna.ToUnicode(nameRaw)
|
||||
if err != nil {
|
||||
nameUnicode = nameRaw // Fallback to raw name on error.
|
||||
} else {
|
||||
// Avoid pointless duplication.
|
||||
if nameUnicode == nameRaw {
|
||||
nameUnicode = nameRaw
|
||||
}
|
||||
}
|
||||
|
||||
if hasBang {
|
||||
uniqueName = nameIDN + "!" + tag
|
||||
} else {
|
||||
uniqueName = nameIDN
|
||||
}
|
||||
|
||||
return DomainFixedForms{
|
||||
Tag: tag,
|
||||
NameRaw: nameRaw,
|
||||
NameIDN: nameIDN,
|
||||
NameUnicode: nameUnicode,
|
||||
UniqueName: uniqueName,
|
||||
HasBang: hasBang,
|
||||
}
|
||||
}
|
||||
133
pkg/domaintags/domaintags_test.go
Normal file
133
pkg/domaintags/domaintags_test.go
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
package domaintags
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_MakeDomainFixForms(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
wantTag string
|
||||
wantNameRaw string
|
||||
wantNameIDN string
|
||||
wantNameUnicode string
|
||||
wantUniqueName string
|
||||
wantHasBang bool
|
||||
}{
|
||||
{
|
||||
name: "simple domain",
|
||||
input: "example.com",
|
||||
wantTag: "",
|
||||
wantNameRaw: "example.com",
|
||||
wantNameIDN: "example.com",
|
||||
wantNameUnicode: "example.com",
|
||||
wantUniqueName: "example.com",
|
||||
wantHasBang: false,
|
||||
},
|
||||
{
|
||||
name: "domain with tag",
|
||||
input: "example.com!mytag",
|
||||
wantTag: "mytag",
|
||||
wantNameRaw: "example.com",
|
||||
wantNameIDN: "example.com",
|
||||
wantNameUnicode: "example.com",
|
||||
wantUniqueName: "example.com!mytag",
|
||||
wantHasBang: true,
|
||||
},
|
||||
{
|
||||
name: "domain with empty tag",
|
||||
input: "example.com!",
|
||||
wantTag: "",
|
||||
wantNameRaw: "example.com",
|
||||
wantNameIDN: "example.com",
|
||||
wantNameUnicode: "example.com",
|
||||
wantUniqueName: "example.com!",
|
||||
wantHasBang: true,
|
||||
},
|
||||
{
|
||||
name: "unicode domain",
|
||||
input: "उदाहरण.com",
|
||||
wantTag: "",
|
||||
wantNameRaw: "उदाहरण.com",
|
||||
wantNameIDN: "xn--p1b6ci4b4b3a.com",
|
||||
wantNameUnicode: "उदाहरण.com",
|
||||
wantUniqueName: "xn--p1b6ci4b4b3a.com",
|
||||
wantHasBang: false,
|
||||
},
|
||||
{
|
||||
name: "unicode domain with tag",
|
||||
input: "उदाहरण.com!mytag",
|
||||
wantTag: "mytag",
|
||||
wantNameRaw: "उदाहरण.com",
|
||||
wantNameIDN: "xn--p1b6ci4b4b3a.com",
|
||||
wantNameUnicode: "उदाहरण.com",
|
||||
wantUniqueName: "xn--p1b6ci4b4b3a.com!mytag",
|
||||
wantHasBang: true,
|
||||
},
|
||||
{
|
||||
name: "punycode domain",
|
||||
input: "xn--p1b6ci4b4b3a.com",
|
||||
wantTag: "",
|
||||
wantNameRaw: "xn--p1b6ci4b4b3a.com",
|
||||
wantNameIDN: "xn--p1b6ci4b4b3a.com",
|
||||
wantNameUnicode: "उदाहरण.com",
|
||||
wantUniqueName: "xn--p1b6ci4b4b3a.com",
|
||||
wantHasBang: false,
|
||||
},
|
||||
{
|
||||
name: "punycode domain with tag",
|
||||
input: "xn--p1b6ci4b4b3a.com!mytag",
|
||||
wantTag: "mytag",
|
||||
wantNameRaw: "xn--p1b6ci4b4b3a.com",
|
||||
wantNameIDN: "xn--p1b6ci4b4b3a.com",
|
||||
wantNameUnicode: "उदाहरण.com",
|
||||
wantUniqueName: "xn--p1b6ci4b4b3a.com!mytag",
|
||||
wantHasBang: true,
|
||||
},
|
||||
{
|
||||
name: "mixed case domain",
|
||||
input: "Example.COM",
|
||||
wantTag: "",
|
||||
wantNameRaw: "example.com",
|
||||
wantNameIDN: "example.com",
|
||||
wantNameUnicode: "example.com",
|
||||
wantUniqueName: "example.com",
|
||||
wantHasBang: false,
|
||||
},
|
||||
{
|
||||
name: "mixed case domain with tag",
|
||||
input: "Example.COM!MyTag",
|
||||
wantTag: "MyTag",
|
||||
wantNameRaw: "example.com",
|
||||
wantNameIDN: "example.com",
|
||||
wantNameUnicode: "example.com",
|
||||
wantUniqueName: "example.com!MyTag",
|
||||
wantHasBang: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := MakeDomainFixForms(tt.input)
|
||||
if got.Tag != tt.wantTag {
|
||||
t.Errorf("MakeDomainFixForms() gotTag = %v, want %v", got.Tag, tt.wantTag)
|
||||
}
|
||||
if got.NameRaw != tt.wantNameRaw {
|
||||
t.Errorf("MakeDomainFixForms() gotNameRaw = %v, want %v", got.NameRaw, tt.wantNameRaw)
|
||||
}
|
||||
if got.NameIDN != tt.wantNameIDN {
|
||||
t.Errorf("MakeDomainFixForms() gotNameIDN = %v, want %v", got.NameIDN, tt.wantNameIDN)
|
||||
}
|
||||
if got.NameUnicode != tt.wantNameUnicode {
|
||||
t.Errorf("MakeDomainFixForms() gotNameUnicode = %v, want %v", got.NameUnicode, tt.wantNameUnicode)
|
||||
}
|
||||
if got.UniqueName != tt.wantUniqueName {
|
||||
t.Errorf("MakeDomainFixForms() gotUniqueName = %v, want %v", got.UniqueName, tt.wantUniqueName)
|
||||
}
|
||||
if got.HasBang != tt.wantHasBang {
|
||||
t.Errorf("MakeDomainFixForms() gotHasTag = %v, want %v", got.HasBang, tt.wantHasBang)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
91
pkg/domaintags/permitlist.go
Normal file
91
pkg/domaintags/permitlist.go
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
package domaintags
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type PermitList struct {
|
||||
// If the permit list is "all" or "".
|
||||
all bool
|
||||
items []DomainFixedForms
|
||||
}
|
||||
|
||||
// CompilePermitList compiles a list of domain strings into a PermitList structure. The
|
||||
func CompilePermitList(s string) PermitList {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" || s == "*" || strings.ToLower(s) == "all" {
|
||||
return PermitList{all: true}
|
||||
}
|
||||
|
||||
sl := PermitList{}
|
||||
for _, l := range strings.Split(s, ",") {
|
||||
l = strings.TrimSpace(l)
|
||||
if l == "" { // Skip empty entries. They match nothing.
|
||||
continue
|
||||
}
|
||||
ff := MakeDomainFixForms(l)
|
||||
if ff.HasBang && ff.NameIDN == "" { // Treat empty name as wildcard.
|
||||
ff.NameIDN = "*"
|
||||
}
|
||||
sl.items = append(sl.items, ff)
|
||||
}
|
||||
|
||||
return sl
|
||||
}
|
||||
|
||||
func (pl *PermitList) Permitted(domToCheck string) bool {
|
||||
|
||||
// If the permit list is "all", everything is permitted.
|
||||
if pl.all {
|
||||
return true
|
||||
}
|
||||
|
||||
domToCheckFF := MakeDomainFixForms(domToCheck)
|
||||
|
||||
for _, filterItem := range pl.items {
|
||||
|
||||
// Special case: filter=example.com!* does not match example.com (no tag)
|
||||
if filterItem.Tag == "*" && !domToCheckFF.HasBang {
|
||||
continue
|
||||
}
|
||||
// Special case: filter=example.com!* does not match example.com! (empty tag)
|
||||
if filterItem.Tag == "*" && domToCheckFF.HasBang && domToCheckFF.Tag == "" {
|
||||
continue
|
||||
}
|
||||
// Special case: filter=example.com! does not match example.com!tag
|
||||
if filterItem.HasBang && filterItem.Tag == "" && domToCheckFF.HasBang && domToCheckFF.Tag != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip if tags don't match
|
||||
if (filterItem.Tag != "*") && (domToCheckFF.Tag != filterItem.Tag) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Now that we know the tag matches, we can focus on the name.
|
||||
|
||||
// `*!tag` or `*` matches everything.
|
||||
if filterItem.NameIDN == "*" {
|
||||
return true
|
||||
}
|
||||
|
||||
// If the name starts with "*." then match the suffix.
|
||||
if strings.HasPrefix(filterItem.NameIDN, "*.") {
|
||||
// example.com matches *.example.com
|
||||
if domToCheckFF.NameIDN == filterItem.NameIDN[2:] || domToCheckFF.NameUnicode == filterItem.NameUnicode[2:] {
|
||||
return true
|
||||
}
|
||||
// foo.example.com matches *.example.com
|
||||
if strings.HasSuffix(domToCheckFF.NameIDN, filterItem.NameIDN[1:]) || strings.HasSuffix(domToCheckFF.NameUnicode, filterItem.NameUnicode[1:]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// No wildcards? Exact match.
|
||||
if filterItem.NameIDN == domToCheckFF.NameIDN || filterItem.NameUnicode == domToCheckFF.NameUnicode {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
110
pkg/domaintags/permitlist_test.go
Normal file
110
pkg/domaintags/permitlist_test.go
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
package domaintags
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestPermitList_Permitted(t *testing.T) {
|
||||
// MakeDomainFixForms is not exported, so we can't directly use it here
|
||||
// to create complex test cases with IDNs easily without duplicating its logic.
|
||||
// However, the existing tests cover a wide range of practical scenarios.
|
||||
// For the purpose of this test, we'll assume MakeDomainFixForms works as expected
|
||||
// and focus on the logic of the Permitted method itself.
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
permitList string
|
||||
domain string
|
||||
expected bool
|
||||
}{
|
||||
// "all" or empty permit list
|
||||
{"all permits everything", "all", "example.com", true},
|
||||
{"all permits everything with tag", "all", "example.com!tag1", true},
|
||||
{"empty string permits everything", "", "example.com", true},
|
||||
{"whitespace string permits everything", " ", "example.com", true},
|
||||
|
||||
// Simple exact matches
|
||||
{"exact match", "example.com", "example.com", true},
|
||||
{"exact match with tag", "example.com!tag1", "example.com!tag1", true},
|
||||
{"exact mismatch domain", "example.com", "google.com", false},
|
||||
{"exact mismatch tag", "example.com!tag1", "example.com!tag2", false},
|
||||
{"exact mismatch domain with tag", "example.com!tag1", "google.com!tag1", false},
|
||||
{"domain with tag not in list without tag", "example.com", "example.com!tag1", false},
|
||||
{"domain without tag not in list with tag", "example.com!tag1", "example.com", false},
|
||||
|
||||
// Wildcard domain name
|
||||
{"wildcard domain matches", "*!tag1", "example.com!tag1", true},
|
||||
{"wildcard domain mismatch tag", "*!tag1", "example.com!tag2", false},
|
||||
{"wildcard domain no tag", "*!tag1", "example.com", false},
|
||||
{"wildcard domain and tag", "*", "example.com!tag1", true},
|
||||
{"wildcard domain and tag no tag", "*", "example.com", true},
|
||||
|
||||
// Wildcard tag
|
||||
{"wildcard tag matches", "example.com!*", "example.com!tag1", true},
|
||||
{"wildcard tag matches no tag", "example.com!*", "example.com", false},
|
||||
{"wildcard tag mismatch domain", "example.com!*", "google.com!tag1", false},
|
||||
|
||||
// Suffix matching
|
||||
{"suffix match base domain", "*.example.com", "example.com", true},
|
||||
{"suffix match subdomain", "*.example.com", "foo.example.com", true},
|
||||
{"suffix match another subdomain", "*.example.com", "foo.bar.example.com", true},
|
||||
{"suffix mismatch different domain", "*.example.com", "google.com", false},
|
||||
{"suffix mismatch partial", "*.example.com", "badexample.com", false},
|
||||
{"suffix match with tag", "*.example.com!tag1", "foo.example.com!tag1", true},
|
||||
{"suffix match base domain with tag", "*.example.com!tag1", "example.com!tag1", true},
|
||||
{"suffix mismatch tag", "*.example.com!tag1", "foo.example.com!tag2", false},
|
||||
{"suffix mismatch domain with tag", "*.example.com!tag1", "google.com!tag1", false},
|
||||
|
||||
// Multiple items in list
|
||||
{"multiple items first match", "google.com,example.com", "google.com", true},
|
||||
{"multiple items second match", "google.com,example.com", "example.com", true},
|
||||
{"multiple items no match", "google.com,example.com", "other.com", false},
|
||||
{"multiple items with tags match", "google.com!tag1,example.com!tag2", "example.com!tag2", true},
|
||||
{"multiple items with tags mismatch", "google.com!tag1,example.com!tag2", "example.com!tag1", false},
|
||||
{"multiple complex items match", "a.com,*.b.com!tag1,c.com!*", "foo.b.com!tag1", true},
|
||||
{"multiple complex items match 2", "a.com,*.b.com!tag1,c.com!*", "c.com!anytag", true},
|
||||
{"multiple complex items no match", "a.com,*.b.com!tag1,c.com!*", "foo.b.com!tag2", false},
|
||||
|
||||
// IDN/Unicode cases (assuming MakeDomainFixForms works)
|
||||
{"IDN exact match punycode", "xn--e1a4c.com", "xn--e1a4c.com", true}, // д.com
|
||||
{"IDN exact match unicode", "д.com", "д.com", true},
|
||||
{"IDN mixed match", "xn--d1a.com", "д.com", true},
|
||||
{"IDN mixed match reversed", "д.com", "xn--d1a.com", true},
|
||||
{"IDN suffix match punycode", "*.xn--e1a4c.com", "sub.xn--e1a4c.com", true},
|
||||
{"IDN suffix match unicode", "*.д.com", "sub.д.com", true},
|
||||
{"IDN suffix match mixed", "*.xn--d1a.com", "sub.д.com", true},
|
||||
{"IDN suffix match mixed reversed", "*.д.com", "sub.xn--d1a.com", true},
|
||||
{"IDN suffix match base", "*.д.com", "д.com", true},
|
||||
|
||||
// Edge cases
|
||||
{"empty list", " ", "example.com", true}, // TrimSpace makes it "", which is "all"
|
||||
{"list with empty items", "one.com,,two.com", "one.com", true},
|
||||
{"list with empty items 2", "one.com,,two.com", "two.com", true},
|
||||
{"list with empty items no match", "one.com,,two.com", "three.com", false},
|
||||
{"no match on empty list", "nonexistent", "example.com", false},
|
||||
|
||||
// Weird backwards compatibility with no tag being different than empty tag
|
||||
{"empty tag vs no tag mismatch", "example.com", "example.com!foo", false},
|
||||
|
||||
// testMultiFilterTaggedWildcard
|
||||
{"testMultiFilterTaggedWildcard_0", "example.com!*", "example.com!", false},
|
||||
{"testMultiFilterTaggedWildcard_1", "example.com!*", "example.com", false},
|
||||
{"testMultiFilterTaggedWildcard_2", "example.com!*", "example.net", false},
|
||||
{"testMultiFilterTaggedWildcard_3", "example.com!*", "example.com!george", true},
|
||||
{"testMultiFilterTaggedWildcard_4", "example.com!*", "example.com!john", true},
|
||||
|
||||
// testFilterEmptyTagAndNoTag
|
||||
{"testFilterEmptyTagAndNoTag_0", "example.com!,example.com", "example.com!", true},
|
||||
{"testFilterEmptyTagAndNoTag_1", "example.com!,example.com", "example.com", true},
|
||||
{"testFilterEmptyTagAndNoTag_2", "example.com!,example.com", "example.net", false},
|
||||
{"testFilterEmptyTagAndNoTag_3", "example.com!,example.com", "example.com!tag", false},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
pl := CompilePermitList(tc.permitList)
|
||||
got := pl.Permitted(tc.domain)
|
||||
if got != tc.expected {
|
||||
t.Errorf("PermitList(%q).Permitted(%q) = %v; want %v", tc.permitList, tc.domain, got, tc.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -36,6 +36,8 @@ Back-port the ACTUAL results to the expected results:
|
|||
(This is dangerous. You may be committing buggy results to the "expected" files. Carefully inspect the resulting PR.)
|
||||
|
||||
```
|
||||
find . -type f -name \*.ACTUAL -print -delete
|
||||
go test -count=1 ./...
|
||||
cd parse_tests
|
||||
fmtjson *.json *.json.ACTUAL
|
||||
for i in *.ACTUAL ; do f=$(basename $i .ACTUAL) ; cp $i $f ; done
|
||||
|
|
|
|||
|
|
@ -119,6 +119,12 @@ func ExecuteJavascriptString(script []byte, devMode bool, variables map[string]s
|
|||
if err = json.Unmarshal([]byte(str), conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = conf.PostProcess()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,8 +18,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
testDir = "pkg/js/parse_tests"
|
||||
errorDir = "pkg/js/error_tests"
|
||||
testDir = "pkg/js/parse_tests"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
@ -49,9 +48,6 @@ func TestParsedFiles(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, dc := range conf.Domains {
|
||||
dc.UpdateSplitHorizonNames()
|
||||
}
|
||||
|
||||
errs := normalize.ValidateAndNormalizeConfig(conf)
|
||||
if len(errs) != 0 {
|
||||
|
|
@ -115,8 +111,7 @@ func TestParsedFiles(t *testing.T) {
|
|||
var dCount int
|
||||
for _, dc := range conf.Domains {
|
||||
var zoneFile string
|
||||
dc.UpdateSplitHorizonNames()
|
||||
if dc.Metadata[models.DomainTag] != "" {
|
||||
if dc.Tag != "" {
|
||||
zoneFile = filepath.Join(testDir, testName, dc.GetUniqueName()+".zone")
|
||||
} else {
|
||||
zoneFile = filepath.Join(testDir, testName, dc.Name+".zone")
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
"Cloudflare": -1
|
||||
},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
"Cloudflare": -1
|
||||
},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
"Cloudflare": 0
|
||||
},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
@ -56,7 +55,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "diff2.com"
|
||||
},
|
||||
"name": "diff2.com",
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
"Cloudflare": -1
|
||||
},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo1.com"
|
||||
},
|
||||
"name": "foo1.com",
|
||||
|
|
@ -29,7 +28,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "inny"
|
||||
},
|
||||
"name": "inny",
|
||||
|
|
@ -54,7 +52,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "com.inny"
|
||||
},
|
||||
"name": "com.inny",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "2.1.in-addr.arpa"
|
||||
},
|
||||
"name": "2.1.in-addr.arpa",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "sortfoo.com"
|
||||
},
|
||||
"name": "sortfoo.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "nothing.com"
|
||||
},
|
||||
"name": "nothing.com",
|
||||
|
|
@ -15,7 +14,6 @@
|
|||
"auto_dnssec": "on",
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "with.com"
|
||||
},
|
||||
"name": "with.com",
|
||||
|
|
@ -26,7 +24,6 @@
|
|||
"auto_dnssec": "off",
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "without.com"
|
||||
},
|
||||
"name": "without.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
"Cloudflare": -1
|
||||
},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
@ -38,7 +37,6 @@
|
|||
"Cloudflare": -1
|
||||
},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "bar.foo.com"
|
||||
},
|
||||
"name": "bar.foo.com",
|
||||
|
|
@ -65,7 +63,6 @@
|
|||
"Cloudflare": -1
|
||||
},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.edu"
|
||||
},
|
||||
"name": "foo.edu",
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
"Cloudflare": -1
|
||||
},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.net"
|
||||
},
|
||||
"name": "foo.net",
|
||||
|
|
@ -70,7 +69,6 @@
|
|||
"Cloudflare": -1
|
||||
},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.tld"
|
||||
},
|
||||
"name": "foo.tld",
|
||||
|
|
@ -104,7 +102,6 @@
|
|||
"Cloudflare": -1
|
||||
},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "bar.foo.tld"
|
||||
},
|
||||
"name": "bar.foo.tld",
|
||||
|
|
@ -138,7 +135,6 @@
|
|||
"Cloudflare": -1
|
||||
},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.help"
|
||||
},
|
||||
"name": "foo.help",
|
||||
|
|
@ -181,7 +177,6 @@
|
|||
"Cloudflare": -1
|
||||
},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "bar.foo.help"
|
||||
},
|
||||
"name": "bar.foo.help",
|
||||
|
|
@ -224,7 +219,6 @@
|
|||
"Cloudflare": -1
|
||||
},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.here"
|
||||
},
|
||||
"name": "foo.here",
|
||||
|
|
@ -283,7 +277,6 @@
|
|||
"Cloudflare": -1
|
||||
},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "example.com"
|
||||
},
|
||||
"name": "example.com",
|
||||
|
|
@ -342,10 +335,9 @@
|
|||
"Cloudflare": -1
|
||||
},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "d\u00fcsseldorf.example.net"
|
||||
"dnscontrol_uniquename": "xn--dsseldorf-q9a.example.net"
|
||||
},
|
||||
"name": "d\u00fcsseldorf.example.net",
|
||||
"name": "xn--dsseldorf-q9a.example.net",
|
||||
"records": [
|
||||
{
|
||||
"filepos": "[line:94:5]",
|
||||
|
|
@ -417,10 +409,9 @@
|
|||
"Cloudflare": -1
|
||||
},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "\u00fc.example.net"
|
||||
"dnscontrol_uniquename": "xn--tda.example.net"
|
||||
},
|
||||
"name": "\u00fc.example.net",
|
||||
"name": "xn--tda.example.net",
|
||||
"records": [
|
||||
{
|
||||
"filepos": "[line:116:5]",
|
||||
|
|
@ -492,7 +483,6 @@
|
|||
"Cloudflare": -1
|
||||
},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "example.tld"
|
||||
},
|
||||
"name": "example.tld",
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
"Cloudflare": -1
|
||||
},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "domain.tld"
|
||||
},
|
||||
"name": "domain.tld",
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
"Cloudflare": -1
|
||||
},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "domain.tld"
|
||||
},
|
||||
"name": "domain.tld",
|
||||
|
|
@ -114,7 +113,6 @@
|
|||
"Cloudflare": -1
|
||||
},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "sub.domain.tld"
|
||||
},
|
||||
"name": "sub.domain.tld",
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
"bind": -1
|
||||
},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "3.2.1.in-addr.arpa"
|
||||
},
|
||||
"name": "3.2.1.in-addr.arpa",
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
"bind": -1
|
||||
},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "8.9.in-addr.arpa"
|
||||
},
|
||||
"name": "8.9.in-addr.arpa",
|
||||
|
|
@ -39,7 +38,6 @@
|
|||
"bind": -1
|
||||
},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "example.com"
|
||||
},
|
||||
"name": "example.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com",
|
||||
"ns_ttl": "86400"
|
||||
},
|
||||
|
|
@ -15,7 +14,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "bar.com",
|
||||
"ns_ttl": "300"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
"Cloudflare": -1
|
||||
},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@
|
|||
"otherconfig": -1
|
||||
},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "example.com"
|
||||
},
|
||||
"name": "example.com",
|
||||
|
|
@ -66,7 +65,8 @@
|
|||
"type": "A"
|
||||
}
|
||||
],
|
||||
"registrar": "Third-Party"
|
||||
"registrar": "Third-Party",
|
||||
"tag": "inside"
|
||||
},
|
||||
{
|
||||
"dnsProviders": {
|
||||
|
|
@ -86,14 +86,14 @@
|
|||
"type": "A"
|
||||
}
|
||||
],
|
||||
"registrar": "Third-Party"
|
||||
"registrar": "Third-Party",
|
||||
"tag": "outside"
|
||||
},
|
||||
{
|
||||
"dnsProviders": {
|
||||
"bind": -1
|
||||
},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "example.net"
|
||||
},
|
||||
"name": "example.net",
|
||||
|
|
@ -147,7 +147,8 @@
|
|||
"type": "A"
|
||||
}
|
||||
],
|
||||
"registrar": "Third-Party"
|
||||
"registrar": "Third-Party",
|
||||
"tag": "inside"
|
||||
},
|
||||
{
|
||||
"dnsProviders": {
|
||||
|
|
@ -181,14 +182,14 @@
|
|||
"type": "A"
|
||||
}
|
||||
],
|
||||
"registrar": "Third-Party"
|
||||
"registrar": "Third-Party",
|
||||
"tag": "outside"
|
||||
},
|
||||
{
|
||||
"dnsProviders": {
|
||||
"bind": -1
|
||||
},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "empty.example.net"
|
||||
},
|
||||
"name": "empty.example.net",
|
||||
|
|
@ -215,8 +216,7 @@
|
|||
"bind": -1
|
||||
},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "example-b.net"
|
||||
"dnscontrol_uniquename": "example-b.net!"
|
||||
},
|
||||
"name": "example-b.net",
|
||||
"records": [
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@
|
|||
"type": "A"
|
||||
}
|
||||
],
|
||||
"registrar": "Third-Party"
|
||||
"registrar": "Third-Party",
|
||||
"tag": "external"
|
||||
},
|
||||
{
|
||||
"dnsProviders": {
|
||||
|
|
@ -51,7 +52,8 @@
|
|||
"type": "A"
|
||||
}
|
||||
],
|
||||
"registrar": "Third-Party"
|
||||
"registrar": "Third-Party",
|
||||
"tag": "internal"
|
||||
}
|
||||
],
|
||||
"registrars": [
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com",
|
||||
"zone_id": "Z2FTEDLFRTZ"
|
||||
},
|
||||
|
|
@ -37,7 +36,8 @@
|
|||
"type": "R53_ALIAS"
|
||||
}
|
||||
],
|
||||
"registrar": "none"
|
||||
"registrar": "none",
|
||||
"tag": "internal"
|
||||
}
|
||||
],
|
||||
"registrars": []
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "unsafe.com"
|
||||
},
|
||||
"name": "unsafe.com",
|
||||
|
|
@ -15,7 +14,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "safe.com"
|
||||
},
|
||||
"name": "safe.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "example.com"
|
||||
},
|
||||
"name": "example.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "a9993e364706816aba3e25717850c26c9cd0d89d"
|
||||
},
|
||||
"name": "a9993e364706816aba3e25717850c26c9cd0d89d",
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
"bind": -1
|
||||
},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "6.10.in-addr.arpa"
|
||||
},
|
||||
"name": "6.10.in-addr.arpa",
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
"bind": -1
|
||||
},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "d.c.b.a.1.1.0.2.ip6.arpa"
|
||||
},
|
||||
"name": "d.c.b.a.1.1.0.2.ip6.arpa",
|
||||
|
|
@ -38,7 +37,6 @@
|
|||
"bind": -1
|
||||
},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "8.b.d.0.1.0.0.2.ip6.arpa"
|
||||
},
|
||||
"name": "8.b.d.0.1.0.0.2.ip6.arpa",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
{
|
||||
"dnsProviders": {},
|
||||
"meta": {
|
||||
"dnscontrol_tag": "",
|
||||
"dnscontrol_uniquename": "foo.com"
|
||||
},
|
||||
"name": "foo.com",
|
||||
|
|
|
|||
|
|
@ -32,6 +32,10 @@ func TestImportTransform(t *testing.T) {
|
|||
cfg := &models.DNSConfig{
|
||||
Domains: []*models.DomainConfig{src, dst},
|
||||
}
|
||||
err := cfg.PostProcess()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if errs := ValidateAndNormalizeConfig(cfg); len(errs) != 0 {
|
||||
for _, err := range errs {
|
||||
t.Error(err)
|
||||
|
|
|
|||
|
|
@ -584,10 +584,6 @@ func ValidateAndNormalizeConfig(config *models.DNSConfig) (errs []error) {
|
|||
|
||||
// processSplitHorizonDomains finds "domain.tld!tag" domains and pre-processes them.
|
||||
func processSplitHorizonDomains(config *models.DNSConfig) error {
|
||||
// Parse out names and tags.
|
||||
for _, d := range config.Domains {
|
||||
d.UpdateSplitHorizonNames()
|
||||
}
|
||||
|
||||
// Verify uniquenames are unique
|
||||
seen := map[string]bool{}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import (
|
|||
// CLI is an abstraction around the CLI.
|
||||
type CLI interface {
|
||||
Printer
|
||||
StartDomain(domain string)
|
||||
StartDomain(dc *models.DomainConfig)
|
||||
StartDNSProvider(name string, skip bool)
|
||||
EndProvider(name string, numCorrections int, err error)
|
||||
EndProvider2(name string, numCorrections int)
|
||||
|
|
@ -89,8 +89,12 @@ type ConsolePrinter struct {
|
|||
}
|
||||
|
||||
// StartDomain is called at the start of each domain.
|
||||
func (c ConsolePrinter) StartDomain(domain string) {
|
||||
fmt.Fprintf(c.Writer, "******************** Domain: %s\n", domain)
|
||||
func (c ConsolePrinter) StartDomain(dc *models.DomainConfig) {
|
||||
if dc.Name == dc.NameUnicode {
|
||||
fmt.Fprintf(c.Writer, "******************** Domain: %s\n", dc.Name)
|
||||
} else {
|
||||
fmt.Fprintf(c.Writer, "******************** Domain: %s (%s)\n", dc.NameUnicode, dc.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintCorrection is called to print/format each correction.
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/StackExchange/dnscontrol/v4/models"
|
||||
"github.com/StackExchange/dnscontrol/v4/pkg/bindserial"
|
||||
"github.com/StackExchange/dnscontrol/v4/pkg/diff2"
|
||||
"github.com/StackExchange/dnscontrol/v4/pkg/domaintags"
|
||||
"github.com/StackExchange/dnscontrol/v4/pkg/prettyzone"
|
||||
"github.com/StackExchange/dnscontrol/v4/pkg/printer"
|
||||
"github.com/StackExchange/dnscontrol/v4/providers"
|
||||
|
|
@ -167,20 +168,23 @@ func (c *bindProvider) GetZoneRecords(domain string, meta map[string]string) (mo
|
|||
if _, err := os.Stat(c.directory); os.IsNotExist(err) {
|
||||
printer.Printf("\nWARNING: BIND directory %q does not exist! (will create)\n", c.directory)
|
||||
}
|
||||
_, okTag := meta[models.DomainTag]
|
||||
_, okUnique := meta[models.DomainUniqueName]
|
||||
if !okTag && !okUnique {
|
||||
// This layering violation is needed for tests only.
|
||||
// Otherwise, this is set already.
|
||||
// Note: In this situation there is no "uniquename" or "tag".
|
||||
zonefile = filepath.Join(c.directory,
|
||||
makeFileName(c.filenameformat, domain, domain, ""))
|
||||
} else {
|
||||
zonefile = filepath.Join(c.directory,
|
||||
makeFileName(c.filenameformat,
|
||||
meta[models.DomainUniqueName], domain, meta[models.DomainTag]),
|
||||
)
|
||||
ff := domaintags.DomainFixedForms{
|
||||
Tag: meta[models.DomainTag],
|
||||
NameRaw: meta[models.DomainNameRaw],
|
||||
NameIDN: domain,
|
||||
NameUnicode: meta[models.DomainNameUnicode],
|
||||
UniqueName: meta[models.DomainUniqueName],
|
||||
}
|
||||
zonefile = filepath.Join(c.directory,
|
||||
makeFileName(
|
||||
c.filenameformat,
|
||||
ff,
|
||||
),
|
||||
)
|
||||
//fmt.Printf("DEBUG: Reading zonefile %q\n", zonefile)
|
||||
//fmt.Printf("DEBUG: Meta %+v\n", meta)
|
||||
//fmt.Printf("DEBUG: Domain Names %+v\n", ff)
|
||||
|
||||
content, err := os.ReadFile(zonefile)
|
||||
if os.IsNotExist(err) {
|
||||
// If the file doesn't exist, that's not an error. Just informational.
|
||||
|
|
@ -273,8 +277,16 @@ func (c *bindProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, foundR
|
|||
}
|
||||
|
||||
zonefile = filepath.Join(c.directory,
|
||||
makeFileName(c.filenameformat,
|
||||
dc.Metadata[models.DomainUniqueName], dc.Name, dc.Metadata[models.DomainTag]),
|
||||
makeFileName(
|
||||
c.filenameformat,
|
||||
domaintags.DomainFixedForms{
|
||||
Tag: dc.Tag,
|
||||
NameRaw: dc.NameRaw,
|
||||
NameIDN: dc.Name,
|
||||
NameUnicode: dc.NameUnicode,
|
||||
UniqueName: dc.UniqueName,
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
// We only change the serial number if there is a change.
|
||||
|
|
|
|||
|
|
@ -3,18 +3,23 @@ package bind
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/v4/pkg/domaintags"
|
||||
)
|
||||
|
||||
// makeFileName uses format to generate a zone's filename. See the
|
||||
func makeFileName(format, uniquename, domain, tag string) string {
|
||||
// fmt.Printf("DEBUG: makeFileName(%q, %q, %q, %q)\n", format, uniquename, domain, tag)
|
||||
func makeFileName(format string, ff domaintags.DomainFixedForms) string {
|
||||
//fmt.Printf("DEBUG: makeFileName(%q, %+v)\n", format, ff)
|
||||
nameRaw := ff.NameRaw
|
||||
nameIDN := ff.NameIDN
|
||||
nameUnicode := ff.NameUnicode
|
||||
uniquename := ff.UniqueName
|
||||
tag := ff.Tag
|
||||
if format == "" {
|
||||
fmt.Fprintf(os.Stderr, "BUG: makeFileName called with null format\n")
|
||||
return uniquename
|
||||
panic("BUG: makeFileName called with null format")
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
|
|
@ -36,11 +41,17 @@ func makeFileName(format, uniquename, domain, tag string) string {
|
|||
tok = tokens[pos]
|
||||
switch tok {
|
||||
case "D":
|
||||
b.WriteString(domain)
|
||||
b.WriteString(nameRaw)
|
||||
case "T":
|
||||
b.WriteString(tag)
|
||||
case "U":
|
||||
b.WriteString(uniquename)
|
||||
case "I":
|
||||
b.WriteString(nameIDN)
|
||||
case "N":
|
||||
b.WriteString(nameUnicode)
|
||||
case "%":
|
||||
b.WriteString("%")
|
||||
case "?":
|
||||
if pos == lastpos {
|
||||
b.WriteString("%(format may not end in %?)")
|
||||
|
|
|
|||
|
|
@ -3,12 +3,25 @@ package bind
|
|||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/v4/pkg/domaintags"
|
||||
)
|
||||
|
||||
func Test_makeFileName(t *testing.T) {
|
||||
uu := "uni"
|
||||
dd := "domy"
|
||||
tt := "tagy"
|
||||
ff := domaintags.DomainFixedForms{
|
||||
NameRaw: "raw",
|
||||
NameIDN: "idn",
|
||||
NameUnicode: "unicode",
|
||||
UniqueName: "unique!taga",
|
||||
Tag: "tagb",
|
||||
}
|
||||
tagless := domaintags.DomainFixedForms{
|
||||
NameRaw: "raw",
|
||||
NameIDN: "idn",
|
||||
NameUnicode: "unicode",
|
||||
UniqueName: "unique",
|
||||
Tag: "",
|
||||
}
|
||||
fmtDefault := "%U.zone"
|
||||
fmtBasic := "%U - %T - %D"
|
||||
fmtBk1 := "db_%U" // Something I've seen in books on DNS
|
||||
|
|
@ -19,35 +32,42 @@ func Test_makeFileName(t *testing.T) {
|
|||
fmtErrorUnk := "literal%o" // Unknown % verb
|
||||
|
||||
type args struct {
|
||||
format string
|
||||
uniquename string
|
||||
domain string
|
||||
tag string
|
||||
format string
|
||||
ff domaintags.DomainFixedForms
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{"literal", args{"literal", uu, dd, tt}, "literal"},
|
||||
{"basic", args{fmtBasic, uu, dd, tt}, "uni - tagy - domy"},
|
||||
{"solo", args{"%D", uu, dd, tt}, "domy"},
|
||||
{"front", args{"%Daaa", uu, dd, tt}, "domyaaa"},
|
||||
{"tail", args{"bbb%D", uu, dd, tt}, "bbbdomy"},
|
||||
{"def", args{fmtDefault, uu, dd, tt}, "uni.zone"},
|
||||
{"bk1", args{fmtBk1, uu, dd, tt}, "db_uni"},
|
||||
{"bk2", args{fmtBk2, uu, dd, tt}, "db_tagy_domy"},
|
||||
{"fanWI", args{fmtFancy, uu, dd, tt}, "tagy_domy.zone"},
|
||||
{"fanWO", args{fmtFancy, uu, dd, ""}, "domy.zone"},
|
||||
{"errP", args{fmtErrorPct, uu, dd, tt}, "literal%(format may not end in %)"},
|
||||
{"errQ", args{fmtErrorOpt, uu, dd, tt}, "literal%(format may not end in %?)"},
|
||||
{"errU", args{fmtErrorUnk, uu, dd, tt}, "literal%(unknown %verb %o)"},
|
||||
{"literal", args{"literal", ff}, "literal"},
|
||||
{"middle", args{"mid%Dle", ff}, "midrawle"},
|
||||
{"D", args{"%D", ff}, "raw"},
|
||||
{"I", args{"%I", ff}, "idn"},
|
||||
{"N", args{"%N", ff}, "unicode"},
|
||||
{"T", args{"%T", ff}, "tagb"},
|
||||
{"x1", args{"XX%?xYY", ff}, "XXxYY"},
|
||||
{"x2", args{"AA%?xBB", tagless}, "AABB"},
|
||||
{"U", args{"%U", ff}, "unique!taga"},
|
||||
{"percent", args{"%%", ff}, "%"},
|
||||
//
|
||||
{"default", args{fmtDefault, ff}, "unique!taga.zone"},
|
||||
{"basic", args{fmtBasic, ff}, "unique!taga - tagb - raw"},
|
||||
{"front", args{"%Daaa", ff}, "rawaaa"},
|
||||
{"tail", args{"bbb%D", ff}, "bbbraw"},
|
||||
{"bk1", args{fmtBk1, ff}, "db_unique!taga"},
|
||||
{"bk2", args{fmtBk2, ff}, "db_tagb_raw"},
|
||||
{"fanWI", args{fmtFancy, ff}, "tagb_raw.zone"},
|
||||
{"fanWO", args{fmtFancy, tagless}, "raw.zone"},
|
||||
{"errP", args{fmtErrorPct, ff}, "literal%(format may not end in %)"},
|
||||
{"errQ", args{fmtErrorOpt, ff}, "literal%(format may not end in %?)"},
|
||||
{"errU", args{fmtErrorUnk, ff}, "literal%(unknown %verb %o)"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := makeFileName(tt.args.format, tt.args.uniquename, tt.args.domain, tt.args.tag); got != tt.want {
|
||||
t.Errorf("makeFileName() = %v, want %v", got, tt.want)
|
||||
if got := makeFileName(tt.args.format, tt.args.ff); got != tt.want {
|
||||
t.Errorf("makeFileName(%q) = %q, want %q", tt.args.format, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ func (dsp *powerdnsProvider) getDiff2DomainCorrections(dc *models.DomainConfig,
|
|||
}
|
||||
}
|
||||
|
||||
domainVariant := dsp.zoneName(dc.Name, dc.Metadata[models.DomainTag])
|
||||
domainVariant := dsp.zoneName(dc.Name, dc.Tag)
|
||||
|
||||
// only append a Correction if there are any, otherwise causes an error when sending an empty rrset
|
||||
if len(rrDeleteSets) > 0 {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import (
|
|||
|
||||
// getDNSSECCorrections returns corrections that update a domain's DNSSEC state.
|
||||
func (dsp *powerdnsProvider) getDNSSECCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||
domainVariant := dsp.zoneName(dc.Name, dc.Metadata[models.DomainTag])
|
||||
domainVariant := dsp.zoneName(dc.Name, dc.Tag)
|
||||
zoneCryptokeys, getErr := dsp.client.Cryptokeys().ListCryptokeys(context.Background(), dsp.ServerName, domainVariant)
|
||||
if getErr != nil {
|
||||
if _, ok := getErr.(pdnshttp.ErrNotFound); ok {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue