From 1b2f5d4d340c1774b111db71d81d2a7ff5271729 Mon Sep 17 00:00:00 2001 From: Tom Limoncelli <6293917+tlimoncelli@users.noreply.github.com> Date: Sat, 29 Nov 2025 12:17:44 -0500 Subject: [PATCH] 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 --- commands/commands.go | 36 ----- commands/commands_test.go | 127 ----------------- commands/getZones.go | 5 +- commands/gz_test.go | 2 +- commands/ppreviewPush.go | 24 +--- commands/ppreviewPush_test.go | 2 +- documentation/provider/bind.md | 30 ++-- integrationTest/helpers_integration_test.go | 4 +- models/dns.go | 17 +-- models/domain.go | 85 +++++------ models/domain_test.go | 68 --------- models/t_caa.go | 2 +- pkg/domaintags/domaintags.go | 81 +++++++++++ pkg/domaintags/domaintags_test.go | 133 ++++++++++++++++++ pkg/domaintags/permitlist.go | 91 ++++++++++++ pkg/domaintags/permitlist_test.go | 110 +++++++++++++++ pkg/js/README-parse_tests.md | 2 + pkg/js/js.go | 6 + pkg/js/js_test.go | 9 +- pkg/js/parse_tests/001-basic.json | 1 - pkg/js/parse_tests/002-ttl.json | 1 - pkg/js/parse_tests/003-meta.json | 1 - pkg/js/parse_tests/004-ips.json | 1 - pkg/js/parse_tests/005-ignored-records.json | 2 - pkg/js/parse_tests/006-transforms.json | 1 - .../parse_tests/007-importTransformTTL.json | 3 - pkg/js/parse_tests/008-import.json | 1 - pkg/js/parse_tests/009-reverse.json | 1 - pkg/js/parse_tests/010-alias.json | 1 - pkg/js/parse_tests/011-cfRedirect.json | 1 - pkg/js/parse_tests/012-duration.json | 1 - pkg/js/parse_tests/013-mx.json | 1 - pkg/js/parse_tests/014-caa.json | 1 - pkg/js/parse_tests/015-tlsa.json | 1 - pkg/js/parse_tests/017-txt.json | 1 - pkg/js/parse_tests/018-dkim.json | 1 - pkg/js/parse_tests/019-r53-alias.json | 1 - pkg/js/parse_tests/020-complexRequire.json | 1 - pkg/js/parse_tests/021-srv.json | 1 - pkg/js/parse_tests/022-sshfp.json | 1 - .../parse_tests/023-ignored-glob-records.json | 1 - pkg/js/parse_tests/024-json-import.json | 1 - pkg/js/parse_tests/025-autodnssec.json | 3 - pkg/js/parse_tests/026-azure-alias.json | 1 - pkg/js/parse_tests/027-ds.json | 1 - pkg/js/parse_tests/028-dextend.json | 3 - pkg/js/parse_tests/029-dextendsub.json | 18 +-- ...zone => xn--dsseldorf-q9a.example.net.zone} | 0 ...ample.net.zone => xn--tda.example.net.zone} | 0 pkg/js/parse_tests/030-dextenddoc.json | 1 - pkg/js/parse_tests/031-dextendnames.json | 2 - pkg/js/parse_tests/032-reverseip.json | 1 - pkg/js/parse_tests/033-revextend.json | 2 - pkg/js/parse_tests/034-nameserver-ttl.json | 2 - pkg/js/parse_tests/035-naptr.json | 1 - pkg/js/parse_tests/036-dextendcf.json | 1 - pkg/js/parse_tests/037-splithor.json | 18 +-- pkg/js/parse_tests/038-soa.json | 1 - pkg/js/parse_tests/039-include.json | 6 +- pkg/js/parse_tests/040-cfWorkerRoute.json | 1 - pkg/js/parse_tests/040-r53-zone.json | 4 +- pkg/js/parse_tests/043-safety.json | 2 - pkg/js/parse_tests/044-ensureabsent.json | 1 - pkg/js/parse_tests/045-loc.json | 1 - pkg/js/parse_tests/046-DHCID.json | 1 - pkg/js/parse_tests/047-DNAME.json | 1 - pkg/js/parse_tests/047-SVCB.json | 1 - pkg/js/parse_tests/048-DNSKEY.json | 1 - pkg/js/parse_tests/049-json5-require.json | 1 - pkg/js/parse_tests/050-cfSingleRedirect.json | 1 - pkg/js/parse_tests/051-HASH.json | 1 - .../parse_tests/054-b3487_d_extend_rev.json | 1 - pkg/js/parse_tests/055-b3550-ipv6ptr.json | 2 - pkg/js/parse_tests/056-openpgpkey.json | 1 - pkg/js/parse_tests/057-smimea.json | 1 - pkg/normalize/importTransform_test.go | 4 + pkg/normalize/validate.go | 4 - pkg/printer/printer.go | 10 +- providers/bind/bindProvider.go | 42 ++++-- providers/bind/fnames.go | 23 ++- providers/bind/fnames_test.go | 64 ++++++--- providers/powerdns/diff.go | 2 +- providers/powerdns/dnssec.go | 2 +- 83 files changed, 623 insertions(+), 470 deletions(-) delete mode 100644 commands/commands_test.go delete mode 100644 models/domain_test.go create mode 100644 pkg/domaintags/domaintags.go create mode 100644 pkg/domaintags/domaintags_test.go create mode 100644 pkg/domaintags/permitlist.go create mode 100644 pkg/domaintags/permitlist_test.go rename pkg/js/parse_tests/029-dextendsub/{düsseldorf.example.net.zone => xn--dsseldorf-q9a.example.net.zone} (100%) rename pkg/js/parse_tests/029-dextendsub/{ü.example.net.zone => xn--tda.example.net.zone} (100%) diff --git a/commands/commands.go b/commands/commands.go index fbf1265ac..3ebb051f0 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -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 -} diff --git a/commands/commands_test.go b/commands/commands_test.go deleted file mode 100644 index 73f974bbd..000000000 --- a/commands/commands_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/commands/getZones.go b/commands/getZones.go index 4839ba343..80c6327e7 100644 --- a/commands/getZones.go +++ b/commands/getZones.go @@ -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) } diff --git a/commands/gz_test.go b/commands/gz_test.go index 9eb123de3..c53009a07 100644 --- a/commands/gz_test.go +++ b/commands/gz_test.go @@ -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 { diff --git a/commands/ppreviewPush.go b/commands/ppreviewPush.go index f666dbde3..4ee9fbacc 100644 --- a/commands/ppreviewPush.go +++ b/commands/ppreviewPush.go @@ -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) } } diff --git a/commands/ppreviewPush_test.go b/commands/ppreviewPush_test.go index 735749f9b..83dd4c941 100644 --- a/commands/ppreviewPush_test.go +++ b/commands/ppreviewPush_test.go @@ -23,7 +23,7 @@ func Test_whichZonesToProcess(t *testing.T) { } for _, dc := range allDC { - dc.UpdateSplitHorizonNames() + dc.PostProcess() } type args struct { diff --git a/documentation/provider/bind.md b/documentation/provider/bind.md index 0a485320b..5136e0795 100644 --- a/documentation/provider/bind.md +++ b/documentation/provider/bind.md @@ -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 diff --git a/integrationTest/helpers_integration_test.go b/integrationTest/helpers_integration_test.go index 2385b796c..a289513f2 100644 --- a/integrationTest/helpers_integration_test.go +++ b/integrationTest/helpers_integration_test.go @@ -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 { diff --git a/models/dns.go b/models/dns.go index 524a3e007..384aea4d7 100644 --- a/models/dns.go +++ b/models/dns.go @@ -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 } diff --git a/models/domain.go b/models/domain.go index f978b8868..845b1b460 100644 --- a/models/domain.go +++ b/models/domain.go @@ -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. diff --git a/models/domain_test.go b/models/domain_test.go deleted file mode 100644 index 140b8edff..000000000 --- a/models/domain_test.go +++ /dev/null @@ -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]) - } - }) - } -} diff --git a/models/t_caa.go b/models/t_caa.go index 73ced3a13..53e94a137 100644 --- a/models/t_caa.go +++ b/models/t_caa.go @@ -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 diff --git a/pkg/domaintags/domaintags.go b/pkg/domaintags/domaintags.go new file mode 100644 index 000000000..adf07d810 --- /dev/null +++ b/pkg/domaintags/domaintags.go @@ -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, + } +} diff --git a/pkg/domaintags/domaintags_test.go b/pkg/domaintags/domaintags_test.go new file mode 100644 index 000000000..f6c010c33 --- /dev/null +++ b/pkg/domaintags/domaintags_test.go @@ -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) + } + }) + } +} diff --git a/pkg/domaintags/permitlist.go b/pkg/domaintags/permitlist.go new file mode 100644 index 000000000..b9e8e904b --- /dev/null +++ b/pkg/domaintags/permitlist.go @@ -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 +} diff --git a/pkg/domaintags/permitlist_test.go b/pkg/domaintags/permitlist_test.go new file mode 100644 index 000000000..d6845a50b --- /dev/null +++ b/pkg/domaintags/permitlist_test.go @@ -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) + } + }) + } +} diff --git a/pkg/js/README-parse_tests.md b/pkg/js/README-parse_tests.md index 0db9c1b7a..3010dfe3d 100644 --- a/pkg/js/README-parse_tests.md +++ b/pkg/js/README-parse_tests.md @@ -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 diff --git a/pkg/js/js.go b/pkg/js/js.go index 6c614d4c5..52708b9cb 100644 --- a/pkg/js/js.go +++ b/pkg/js/js.go @@ -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 } diff --git a/pkg/js/js_test.go b/pkg/js/js_test.go index 8b16a128b..f42c02e4c 100644 --- a/pkg/js/js_test.go +++ b/pkg/js/js_test.go @@ -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") diff --git a/pkg/js/parse_tests/001-basic.json b/pkg/js/parse_tests/001-basic.json index 59609c518..82289bf2e 100644 --- a/pkg/js/parse_tests/001-basic.json +++ b/pkg/js/parse_tests/001-basic.json @@ -11,7 +11,6 @@ "Cloudflare": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/002-ttl.json b/pkg/js/parse_tests/002-ttl.json index 5a4147f0d..48bd3db4a 100644 --- a/pkg/js/parse_tests/002-ttl.json +++ b/pkg/js/parse_tests/002-ttl.json @@ -11,7 +11,6 @@ "Cloudflare": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/003-meta.json b/pkg/js/parse_tests/003-meta.json index a0b3f52d6..21f988ee3 100644 --- a/pkg/js/parse_tests/003-meta.json +++ b/pkg/js/parse_tests/003-meta.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/004-ips.json b/pkg/js/parse_tests/004-ips.json index 230c750cb..4147e135f 100644 --- a/pkg/js/parse_tests/004-ips.json +++ b/pkg/js/parse_tests/004-ips.json @@ -11,7 +11,6 @@ "Cloudflare": 0 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/005-ignored-records.json b/pkg/js/parse_tests/005-ignored-records.json index 0bb768053..142896949 100644 --- a/pkg/js/parse_tests/005-ignored-records.json +++ b/pkg/js/parse_tests/005-ignored-records.json @@ -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", diff --git a/pkg/js/parse_tests/006-transforms.json b/pkg/js/parse_tests/006-transforms.json index f766a1520..2a54d02bf 100644 --- a/pkg/js/parse_tests/006-transforms.json +++ b/pkg/js/parse_tests/006-transforms.json @@ -11,7 +11,6 @@ "Cloudflare": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/007-importTransformTTL.json b/pkg/js/parse_tests/007-importTransformTTL.json index d86d8224c..39027b66e 100644 --- a/pkg/js/parse_tests/007-importTransformTTL.json +++ b/pkg/js/parse_tests/007-importTransformTTL.json @@ -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", diff --git a/pkg/js/parse_tests/008-import.json b/pkg/js/parse_tests/008-import.json index 8049dfb63..ce6010f13 100644 --- a/pkg/js/parse_tests/008-import.json +++ b/pkg/js/parse_tests/008-import.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/009-reverse.json b/pkg/js/parse_tests/009-reverse.json index 96ec46cf5..911740edd 100644 --- a/pkg/js/parse_tests/009-reverse.json +++ b/pkg/js/parse_tests/009-reverse.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "2.1.in-addr.arpa" }, "name": "2.1.in-addr.arpa", diff --git a/pkg/js/parse_tests/010-alias.json b/pkg/js/parse_tests/010-alias.json index 73a364114..cde8d47e1 100644 --- a/pkg/js/parse_tests/010-alias.json +++ b/pkg/js/parse_tests/010-alias.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/011-cfRedirect.json b/pkg/js/parse_tests/011-cfRedirect.json index 5180a8aa5..be71ee946 100644 --- a/pkg/js/parse_tests/011-cfRedirect.json +++ b/pkg/js/parse_tests/011-cfRedirect.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/012-duration.json b/pkg/js/parse_tests/012-duration.json index a1d6f7cd4..6a1637d6b 100644 --- a/pkg/js/parse_tests/012-duration.json +++ b/pkg/js/parse_tests/012-duration.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/013-mx.json b/pkg/js/parse_tests/013-mx.json index 70cb7c8c0..15b25a8d5 100644 --- a/pkg/js/parse_tests/013-mx.json +++ b/pkg/js/parse_tests/013-mx.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/014-caa.json b/pkg/js/parse_tests/014-caa.json index 14669cee7..c6c6fe922 100644 --- a/pkg/js/parse_tests/014-caa.json +++ b/pkg/js/parse_tests/014-caa.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/015-tlsa.json b/pkg/js/parse_tests/015-tlsa.json index 14e4d71e4..7abbd1a40 100644 --- a/pkg/js/parse_tests/015-tlsa.json +++ b/pkg/js/parse_tests/015-tlsa.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/017-txt.json b/pkg/js/parse_tests/017-txt.json index 670b412c0..1a11556b2 100644 --- a/pkg/js/parse_tests/017-txt.json +++ b/pkg/js/parse_tests/017-txt.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/018-dkim.json b/pkg/js/parse_tests/018-dkim.json index 683ce7555..cfad47f93 100644 --- a/pkg/js/parse_tests/018-dkim.json +++ b/pkg/js/parse_tests/018-dkim.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/019-r53-alias.json b/pkg/js/parse_tests/019-r53-alias.json index 71dc75b8a..f500adc26 100644 --- a/pkg/js/parse_tests/019-r53-alias.json +++ b/pkg/js/parse_tests/019-r53-alias.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/020-complexRequire.json b/pkg/js/parse_tests/020-complexRequire.json index 42bdd3116..41d7226c3 100644 --- a/pkg/js/parse_tests/020-complexRequire.json +++ b/pkg/js/parse_tests/020-complexRequire.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "sortfoo.com" }, "name": "sortfoo.com", diff --git a/pkg/js/parse_tests/021-srv.json b/pkg/js/parse_tests/021-srv.json index a5ed0be7e..70940fe89 100644 --- a/pkg/js/parse_tests/021-srv.json +++ b/pkg/js/parse_tests/021-srv.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/022-sshfp.json b/pkg/js/parse_tests/022-sshfp.json index 682efcef5..a1b74c43c 100644 --- a/pkg/js/parse_tests/022-sshfp.json +++ b/pkg/js/parse_tests/022-sshfp.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/023-ignored-glob-records.json b/pkg/js/parse_tests/023-ignored-glob-records.json index fdacff727..b7774bb8b 100644 --- a/pkg/js/parse_tests/023-ignored-glob-records.json +++ b/pkg/js/parse_tests/023-ignored-glob-records.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/024-json-import.json b/pkg/js/parse_tests/024-json-import.json index 0cf167eb4..f08ec91e2 100644 --- a/pkg/js/parse_tests/024-json-import.json +++ b/pkg/js/parse_tests/024-json-import.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/025-autodnssec.json b/pkg/js/parse_tests/025-autodnssec.json index 587dfe1d7..84a07d4d0 100644 --- a/pkg/js/parse_tests/025-autodnssec.json +++ b/pkg/js/parse_tests/025-autodnssec.json @@ -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", diff --git a/pkg/js/parse_tests/026-azure-alias.json b/pkg/js/parse_tests/026-azure-alias.json index ca6716335..1fbf6c8fe 100644 --- a/pkg/js/parse_tests/026-azure-alias.json +++ b/pkg/js/parse_tests/026-azure-alias.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/027-ds.json b/pkg/js/parse_tests/027-ds.json index bb13a682a..d2eb195ed 100644 --- a/pkg/js/parse_tests/027-ds.json +++ b/pkg/js/parse_tests/027-ds.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/028-dextend.json b/pkg/js/parse_tests/028-dextend.json index 2f92ae97c..7790dbb69 100644 --- a/pkg/js/parse_tests/028-dextend.json +++ b/pkg/js/parse_tests/028-dextend.json @@ -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", diff --git a/pkg/js/parse_tests/029-dextendsub.json b/pkg/js/parse_tests/029-dextendsub.json index c241fab13..8c12ca68f 100644 --- a/pkg/js/parse_tests/029-dextendsub.json +++ b/pkg/js/parse_tests/029-dextendsub.json @@ -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", diff --git a/pkg/js/parse_tests/029-dextendsub/düsseldorf.example.net.zone b/pkg/js/parse_tests/029-dextendsub/xn--dsseldorf-q9a.example.net.zone similarity index 100% rename from pkg/js/parse_tests/029-dextendsub/düsseldorf.example.net.zone rename to pkg/js/parse_tests/029-dextendsub/xn--dsseldorf-q9a.example.net.zone diff --git a/pkg/js/parse_tests/029-dextendsub/ü.example.net.zone b/pkg/js/parse_tests/029-dextendsub/xn--tda.example.net.zone similarity index 100% rename from pkg/js/parse_tests/029-dextendsub/ü.example.net.zone rename to pkg/js/parse_tests/029-dextendsub/xn--tda.example.net.zone diff --git a/pkg/js/parse_tests/030-dextenddoc.json b/pkg/js/parse_tests/030-dextenddoc.json index bc7dfa01d..a20ba9f35 100644 --- a/pkg/js/parse_tests/030-dextenddoc.json +++ b/pkg/js/parse_tests/030-dextenddoc.json @@ -11,7 +11,6 @@ "Cloudflare": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "domain.tld" }, "name": "domain.tld", diff --git a/pkg/js/parse_tests/031-dextendnames.json b/pkg/js/parse_tests/031-dextendnames.json index a5c74aa5c..8a276646e 100644 --- a/pkg/js/parse_tests/031-dextendnames.json +++ b/pkg/js/parse_tests/031-dextendnames.json @@ -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", diff --git a/pkg/js/parse_tests/032-reverseip.json b/pkg/js/parse_tests/032-reverseip.json index 01a12e395..1bf7c471c 100644 --- a/pkg/js/parse_tests/032-reverseip.json +++ b/pkg/js/parse_tests/032-reverseip.json @@ -11,7 +11,6 @@ "bind": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "3.2.1.in-addr.arpa" }, "name": "3.2.1.in-addr.arpa", diff --git a/pkg/js/parse_tests/033-revextend.json b/pkg/js/parse_tests/033-revextend.json index daf2e6154..39d68a929 100644 --- a/pkg/js/parse_tests/033-revextend.json +++ b/pkg/js/parse_tests/033-revextend.json @@ -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", diff --git a/pkg/js/parse_tests/034-nameserver-ttl.json b/pkg/js/parse_tests/034-nameserver-ttl.json index 52b412f1e..140d21f5c 100644 --- a/pkg/js/parse_tests/034-nameserver-ttl.json +++ b/pkg/js/parse_tests/034-nameserver-ttl.json @@ -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" }, diff --git a/pkg/js/parse_tests/035-naptr.json b/pkg/js/parse_tests/035-naptr.json index 16b745555..58561e529 100644 --- a/pkg/js/parse_tests/035-naptr.json +++ b/pkg/js/parse_tests/035-naptr.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/036-dextendcf.json b/pkg/js/parse_tests/036-dextendcf.json index 7ca254a62..dbf452bdd 100644 --- a/pkg/js/parse_tests/036-dextendcf.json +++ b/pkg/js/parse_tests/036-dextendcf.json @@ -11,7 +11,6 @@ "Cloudflare": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/037-splithor.json b/pkg/js/parse_tests/037-splithor.json index bf2ac2a93..fabc65da5 100644 --- a/pkg/js/parse_tests/037-splithor.json +++ b/pkg/js/parse_tests/037-splithor.json @@ -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": [ diff --git a/pkg/js/parse_tests/038-soa.json b/pkg/js/parse_tests/038-soa.json index 43aed9b2e..6c63bee83 100644 --- a/pkg/js/parse_tests/038-soa.json +++ b/pkg/js/parse_tests/038-soa.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/039-include.json b/pkg/js/parse_tests/039-include.json index 526cd1338..43e499d12 100644 --- a/pkg/js/parse_tests/039-include.json +++ b/pkg/js/parse_tests/039-include.json @@ -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": [ diff --git a/pkg/js/parse_tests/040-cfWorkerRoute.json b/pkg/js/parse_tests/040-cfWorkerRoute.json index 479624ab7..9673c9bb7 100644 --- a/pkg/js/parse_tests/040-cfWorkerRoute.json +++ b/pkg/js/parse_tests/040-cfWorkerRoute.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/040-r53-zone.json b/pkg/js/parse_tests/040-r53-zone.json index 1e3d68b35..ee7dd322b 100644 --- a/pkg/js/parse_tests/040-r53-zone.json +++ b/pkg/js/parse_tests/040-r53-zone.json @@ -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": [] diff --git a/pkg/js/parse_tests/043-safety.json b/pkg/js/parse_tests/043-safety.json index a6ee5a4f5..82e9d313e 100644 --- a/pkg/js/parse_tests/043-safety.json +++ b/pkg/js/parse_tests/043-safety.json @@ -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", diff --git a/pkg/js/parse_tests/044-ensureabsent.json b/pkg/js/parse_tests/044-ensureabsent.json index f0d3745c4..106da8a17 100644 --- a/pkg/js/parse_tests/044-ensureabsent.json +++ b/pkg/js/parse_tests/044-ensureabsent.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "example.com" }, "name": "example.com", diff --git a/pkg/js/parse_tests/045-loc.json b/pkg/js/parse_tests/045-loc.json index 45be3fcb1..4830e3edb 100644 --- a/pkg/js/parse_tests/045-loc.json +++ b/pkg/js/parse_tests/045-loc.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/046-DHCID.json b/pkg/js/parse_tests/046-DHCID.json index 3c62541a1..919805504 100644 --- a/pkg/js/parse_tests/046-DHCID.json +++ b/pkg/js/parse_tests/046-DHCID.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/047-DNAME.json b/pkg/js/parse_tests/047-DNAME.json index d365ec88a..efb252d5f 100644 --- a/pkg/js/parse_tests/047-DNAME.json +++ b/pkg/js/parse_tests/047-DNAME.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/047-SVCB.json b/pkg/js/parse_tests/047-SVCB.json index 8bfe47a6e..92dd4a5d5 100644 --- a/pkg/js/parse_tests/047-SVCB.json +++ b/pkg/js/parse_tests/047-SVCB.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/048-DNSKEY.json b/pkg/js/parse_tests/048-DNSKEY.json index df7863e4f..a16d0518e 100644 --- a/pkg/js/parse_tests/048-DNSKEY.json +++ b/pkg/js/parse_tests/048-DNSKEY.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/049-json5-require.json b/pkg/js/parse_tests/049-json5-require.json index 0cf167eb4..f08ec91e2 100644 --- a/pkg/js/parse_tests/049-json5-require.json +++ b/pkg/js/parse_tests/049-json5-require.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/050-cfSingleRedirect.json b/pkg/js/parse_tests/050-cfSingleRedirect.json index 7f37f3257..380b1022e 100644 --- a/pkg/js/parse_tests/050-cfSingleRedirect.json +++ b/pkg/js/parse_tests/050-cfSingleRedirect.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/051-HASH.json b/pkg/js/parse_tests/051-HASH.json index b58f325ce..befbd9cb4 100644 --- a/pkg/js/parse_tests/051-HASH.json +++ b/pkg/js/parse_tests/051-HASH.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "a9993e364706816aba3e25717850c26c9cd0d89d" }, "name": "a9993e364706816aba3e25717850c26c9cd0d89d", diff --git a/pkg/js/parse_tests/054-b3487_d_extend_rev.json b/pkg/js/parse_tests/054-b3487_d_extend_rev.json index 33f0eb368..166102876 100644 --- a/pkg/js/parse_tests/054-b3487_d_extend_rev.json +++ b/pkg/js/parse_tests/054-b3487_d_extend_rev.json @@ -11,7 +11,6 @@ "bind": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "6.10.in-addr.arpa" }, "name": "6.10.in-addr.arpa", diff --git a/pkg/js/parse_tests/055-b3550-ipv6ptr.json b/pkg/js/parse_tests/055-b3550-ipv6ptr.json index 9c1fd4326..85cd501f5 100644 --- a/pkg/js/parse_tests/055-b3550-ipv6ptr.json +++ b/pkg/js/parse_tests/055-b3550-ipv6ptr.json @@ -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", diff --git a/pkg/js/parse_tests/056-openpgpkey.json b/pkg/js/parse_tests/056-openpgpkey.json index 7678ddb6c..80f302656 100644 --- a/pkg/js/parse_tests/056-openpgpkey.json +++ b/pkg/js/parse_tests/056-openpgpkey.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/057-smimea.json b/pkg/js/parse_tests/057-smimea.json index e57c5d345..bec0486da 100644 --- a/pkg/js/parse_tests/057-smimea.json +++ b/pkg/js/parse_tests/057-smimea.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/normalize/importTransform_test.go b/pkg/normalize/importTransform_test.go index 4e2c3bb8b..ffd41ee2a 100644 --- a/pkg/normalize/importTransform_test.go +++ b/pkg/normalize/importTransform_test.go @@ -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) diff --git a/pkg/normalize/validate.go b/pkg/normalize/validate.go index a787f4dfa..5c19e00b8 100644 --- a/pkg/normalize/validate.go +++ b/pkg/normalize/validate.go @@ -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{} diff --git a/pkg/printer/printer.go b/pkg/printer/printer.go index 9826b4eed..9f7063d96 100644 --- a/pkg/printer/printer.go +++ b/pkg/printer/printer.go @@ -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. diff --git a/providers/bind/bindProvider.go b/providers/bind/bindProvider.go index 432f679ed..525e8c74f 100644 --- a/providers/bind/bindProvider.go +++ b/providers/bind/bindProvider.go @@ -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. diff --git a/providers/bind/fnames.go b/providers/bind/fnames.go index 51ab58c89..db7b22514 100644 --- a/providers/bind/fnames.go +++ b/providers/bind/fnames.go @@ -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 %?)") diff --git a/providers/bind/fnames_test.go b/providers/bind/fnames_test.go index 34306cc28..66a0bcb8a 100644 --- a/providers/bind/fnames_test.go +++ b/providers/bind/fnames_test.go @@ -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) } }) } diff --git a/providers/powerdns/diff.go b/providers/powerdns/diff.go index 2208e6bd1..d8622b398 100644 --- a/providers/powerdns/diff.go +++ b/providers/powerdns/diff.go @@ -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 { diff --git a/providers/powerdns/dnssec.go b/providers/powerdns/dnssec.go index efaa2ad52..1a4300546 100644 --- a/providers/powerdns/dnssec.go +++ b/providers/powerdns/dnssec.go @@ -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 {