mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
Re-engineer TXT records for simplicity and better compliance (#1063)
TXT records are now handled different. 1. The raw input from dnsconfig.js is passed all the way to the provider. The provider can determine if it can or can't handle such records (auditrecords.go) and processes them internally as such. 2. The CanUseTXTMulti capability is no longer needed. * DSPs now register a table of functions * Use audits for txt record variations * unit tests pass. integration fails. * fix deepcopy problem * rename to AuditRecordSupport * Reduce use of TXTMulti * Remove CanUseTXTMulti * fix Test Skip * fix DO * fix vultr * fix NDC * msdns fixes * Fix powerdns and cloudflare * HEDNS: Fix usage of target field to resolve TXT handling (#1067) * Fix HEXONET Co-authored-by: Robert Blenkinsopp <robert@blenkinsopp.net> Co-authored-by: Jakob Ackermann <das7pad@outlook.com>
This commit is contained in:
@ -74,7 +74,7 @@ func generateFeatureMatrix() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
setDoc("Official Support", providers.DocOfficiallySupported, true)
|
setDoc("Official Support", providers.DocOfficiallySupported, true)
|
||||||
fm.SetSimple("DNS Provider", false, func() bool { return providers.DNSProviderTypes[p] != nil })
|
fm.SetSimple("DNS Provider", false, func() bool { return providers.DNSProviderTypes[p].Initializer != nil })
|
||||||
fm.SetSimple("Registrar", false, func() bool { return providers.RegistrarTypes[p] != nil })
|
fm.SetSimple("Registrar", false, func() bool { return providers.RegistrarTypes[p] != nil })
|
||||||
setCap("ALIAS", providers.CanUseAlias)
|
setCap("ALIAS", providers.CanUseAlias)
|
||||||
setCap("AUTODNSSEC", providers.CanAutoDNSSEC)
|
setCap("AUTODNSSEC", providers.CanAutoDNSSEC)
|
||||||
@ -86,7 +86,6 @@ func generateFeatureMatrix() error {
|
|||||||
setCap("SRV", providers.CanUseSRV)
|
setCap("SRV", providers.CanUseSRV)
|
||||||
setCap("SSHFP", providers.CanUseSSHFP)
|
setCap("SSHFP", providers.CanUseSSHFP)
|
||||||
setCap("TLSA", providers.CanUseTLSA)
|
setCap("TLSA", providers.CanUseTLSA)
|
||||||
setCap("TXTMulti", providers.CanUseTXTMulti)
|
|
||||||
setCap("get-zones", providers.CanGetZones)
|
setCap("get-zones", providers.CanGetZones)
|
||||||
setCap("DS", providers.CanUseDS)
|
setCap("DS", providers.CanUseDS)
|
||||||
setDoc("dual host", providers.DocDualHost, false)
|
setDoc("dual host", providers.DocDualHost, false)
|
||||||
|
@ -132,6 +132,9 @@ DomainLoop:
|
|||||||
if !shouldrun {
|
if !shouldrun {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This is where we should audit?
|
||||||
|
|
||||||
corrections, err := provider.Driver.GetDomainCorrections(dc)
|
corrections, err := provider.Driver.GetDomainCorrections(dc)
|
||||||
out.EndProvider(len(corrections), err)
|
out.EndProvider(len(corrections), err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -939,78 +939,36 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider can manage TXT records with multiple strings">TXTMulti</th>
|
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider can manage TXT records with multiple strings">TXTMulti</th>
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<td class="success">
|
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
|
||||||
</td>
|
|
||||||
<td class="success">
|
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
|
||||||
</td>
|
|
||||||
<td class="success">
|
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
|
||||||
</td>
|
|
||||||
<td class="success">
|
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
|
||||||
</td>
|
|
||||||
<td class="success">
|
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
|
||||||
</td>
|
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<td class="success" data-toggle="tooltip" data-container="body" data-placement="top" title="A broken parser prevents TXTMulti strings from including double-quotes; The total length of all strings can't be longer than 512; and in reality must be shorter due to sloppy validation checks.">
|
|
||||||
<a href="https://github.com/StackExchange/dnscontrol/issues/370"><i class="fa has-tooltip fa-check text-success" aria-hidden="true"></i></a>
|
|
||||||
</td>
|
|
||||||
<td class="success">
|
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
|
||||||
</td>
|
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<td class="success">
|
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
|
||||||
</td>
|
|
||||||
<td class="success">
|
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
|
||||||
</td>
|
|
||||||
<td class="success">
|
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
|
||||||
</td>
|
|
||||||
<td class="success">
|
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
|
||||||
</td>
|
|
||||||
<td class="success">
|
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
|
||||||
</td>
|
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
|
||||||
<td class="success">
|
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
|
||||||
</td>
|
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
|
||||||
<td class="info">
|
|
||||||
<i class="fa fa-circle-o text-info" aria-hidden="true"></i>
|
|
||||||
</td>
|
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
|
||||||
<td class="danger">
|
|
||||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
|
||||||
</td>
|
|
||||||
<td class="success">
|
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
|
||||||
</td>
|
|
||||||
<td class="danger">
|
|
||||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
|
||||||
</td>
|
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<td class="success">
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
</td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<td class="success">
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
</td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<td class="success">
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
</td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<td class="success">
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
</td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -40,6 +40,4 @@ D("example.tld", REG_NONE, DnsProvider(DIGITALOCEAN),
|
|||||||
|
|
||||||
- Digitalocean DNS doesn't support `;` value with CAA-records ([DigitalOcean documentation](https://www.digitalocean.com/docs/networking/dns/how-to/create-caa-records/))
|
- Digitalocean DNS doesn't support `;` value with CAA-records ([DigitalOcean documentation](https://www.digitalocean.com/docs/networking/dns/how-to/create-caa-records/))
|
||||||
- While Digitalocean DNS supports TXT records with multiple strings,
|
- While Digitalocean DNS supports TXT records with multiple strings,
|
||||||
their implementation is lacking. It does not support strings that
|
their length is limited by the max API request of 512 octets.
|
||||||
include double-quotes nor many long strings. The length limits may
|
|
||||||
restrict your ability to use very long DKIM or SPF records.
|
|
||||||
|
@ -65,7 +65,7 @@ provider that does not support the capability.
|
|||||||
|
|
||||||
* Add the capability to the validations in `pkg/normalize/validate.go`
|
* Add the capability to the validations in `pkg/normalize/validate.go`
|
||||||
by adding it to `providerCapabilityChecks`
|
by adding it to `providerCapabilityChecks`
|
||||||
* Some capabilities can't be tested for, such as `CanUseTXTMulti`. If
|
* Some capabilities can't be tested for. If
|
||||||
such testing can't be done, add it to the whitelist in function
|
such testing can't be done, add it to the whitelist in function
|
||||||
`TestCapabilitiesAreFiltered` in
|
`TestCapabilitiesAreFiltered` in
|
||||||
`pkg/normalize/capabilities_test.go`
|
`pkg/normalize/capabilities_test.go`
|
||||||
@ -148,7 +148,7 @@ testgroup("MX", <<< 1
|
|||||||
tc("MX record", <<< 4
|
tc("MX record", <<< 4
|
||||||
mx("@", 10, "foo.com."),
|
mx("@", 10, "foo.com."),
|
||||||
mx("@", 20, "bar.com."),
|
mx("@", 20, "bar.com."),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -156,7 +156,7 @@ Line 1: `testgroup()` gives a name to a group of tests. It also tells
|
|||||||
the system to delete all records for this domain so that the tests
|
the system to delete all records for this domain so that the tests
|
||||||
begin with a blank slate.
|
begin with a blank slate.
|
||||||
|
|
||||||
Line 2:
|
Line 2:
|
||||||
Each `tc()` encodes all the records of a zone. The test framework
|
Each `tc()` encodes all the records of a zone. The test framework
|
||||||
will try to do the smallest changes to bring the zone up to date.
|
will try to do the smallest changes to bring the zone up to date.
|
||||||
In this case, we know the zone is empty, so this will add one MX
|
In this case, we know the zone is empty, so this will add one MX
|
||||||
|
7
go.mod
7
go.mod
@ -43,7 +43,7 @@ require (
|
|||||||
github.com/hexonet/go-sdk v3.5.1+incompatible
|
github.com/hexonet/go-sdk v3.5.1+incompatible
|
||||||
github.com/jarcoal/httpmock v1.0.8 // indirect
|
github.com/jarcoal/httpmock v1.0.8 // indirect
|
||||||
github.com/jinzhu/copier v0.2.5
|
github.com/jinzhu/copier v0.2.5
|
||||||
github.com/juju/testing v0.0.0-20201216035041-2be42bba85f3 // indirect
|
github.com/juju/errors v0.0.0-20200330140219-3fe23663418f // indirect
|
||||||
github.com/kolo/xmlrpc v0.0.0-20201022064351-38db28db192b // indirect
|
github.com/kolo/xmlrpc v0.0.0-20201022064351-38db28db192b // indirect
|
||||||
github.com/miekg/dns v1.1.35
|
github.com/miekg/dns v1.1.35
|
||||||
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
||||||
@ -66,9 +66,8 @@ require (
|
|||||||
github.com/softlayer/softlayer-go v0.0.0-20170804160555-5e1c8cccc730
|
github.com/softlayer/softlayer-go v0.0.0-20170804160555-5e1c8cccc730
|
||||||
github.com/stretchr/objx v0.3.0 // indirect
|
github.com/stretchr/objx v0.3.0 // indirect
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.0
|
||||||
github.com/tdewolff/minify v2.3.6+incompatible
|
github.com/tdewolff/minify v2.3.6+incompatible // indirect
|
||||||
github.com/tdewolff/parse v2.3.4+incompatible // indirect
|
github.com/tdewolff/parse v2.3.4+incompatible // indirect
|
||||||
github.com/tdewolff/test v1.0.6 // indirect
|
|
||||||
github.com/urfave/cli/v2 v2.3.0
|
github.com/urfave/cli/v2 v2.3.0
|
||||||
github.com/vultr/govultr v1.0.0
|
github.com/vultr/govultr v1.0.0
|
||||||
github.com/xddxdd/ottoext v0.0.0-20210101073831-439879ee6281
|
github.com/xddxdd/ottoext v0.0.0-20210101073831-439879ee6281
|
||||||
@ -85,6 +84,6 @@ require (
|
|||||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||||
gopkg.in/ns1/ns1-go.v2 v2.4.3
|
gopkg.in/ns1/ns1-go.v2 v2.4.3
|
||||||
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
|
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||||
)
|
)
|
||||||
|
33
go.sum
33
go.sum
@ -278,31 +278,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
|
|||||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/juju/ansiterm v0.0.0-20160907234532-b99631de12cf/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
|
|
||||||
github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA=
|
|
||||||
github.com/juju/cmd v0.0.0-20171107070456-e74f39857ca0/go.mod h1:yWJQHl73rdSX4DHVKGqkAip+huBslxRwS8m9CrOLq18=
|
|
||||||
github.com/juju/collections v0.0.0-20200605021417-0d0ec82b7271/go.mod h1:5XgO71dV1JClcOJE+4dzdn4HrI5LiyKd7PlVG6eZYhY=
|
|
||||||
github.com/juju/errors v0.0.0-20150916125642-1b5e39b83d18/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
|
|
||||||
github.com/juju/errors v0.0.0-20200330140219-3fe23663418f h1:MCOvExGLpaSIzLYB4iQXEHP4jYVU6vmzLNQPdMVrxnM=
|
github.com/juju/errors v0.0.0-20200330140219-3fe23663418f h1:MCOvExGLpaSIzLYB4iQXEHP4jYVU6vmzLNQPdMVrxnM=
|
||||||
github.com/juju/errors v0.0.0-20200330140219-3fe23663418f/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
|
github.com/juju/errors v0.0.0-20200330140219-3fe23663418f/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
|
||||||
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
|
||||||
github.com/juju/httpprof v0.0.0-20141217160036-14bf14c30767/go.mod h1:+MaLYz4PumRkkyHYeXJ2G5g5cIW0sli2bOfpmbaMV/g=
|
|
||||||
github.com/juju/loggo v0.0.0-20170605014607-8232ab8918d9/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
|
|
||||||
github.com/juju/loggo v0.0.0-20200526014432-9ce3a2e09b5e h1:FdDd7bdI6cjq5vaoYlK1mfQYfF9sF2VZw8VEZMsl5t8=
|
|
||||||
github.com/juju/loggo v0.0.0-20200526014432-9ce3a2e09b5e/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
|
|
||||||
github.com/juju/mutex v0.0.0-20171110020013-1fe2a4bf0a3a/go.mod h1:Y3oOzHH8CQ0Ppt0oCKJ2JFO81/EsWenH5AEqigLH+yY=
|
|
||||||
github.com/juju/retry v0.0.0-20151029024821-62c620325291/go.mod h1:OohPQGsr4pnxwD5YljhQ+TZnuVRYpa5irjugL1Yuif4=
|
|
||||||
github.com/juju/retry v0.0.0-20180821225755-9058e192b216/go.mod h1:OohPQGsr4pnxwD5YljhQ+TZnuVRYpa5irjugL1Yuif4=
|
|
||||||
github.com/juju/testing v0.0.0-20180402130637-44801989f0f7/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
|
|
||||||
github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
|
|
||||||
github.com/juju/testing v0.0.0-20201216035041-2be42bba85f3 h1:ram7cW6jDPTu6cv9xDMwa+tO7RsO4BdsubxrJ4EEw+E=
|
|
||||||
github.com/juju/testing v0.0.0-20201216035041-2be42bba85f3/go.mod h1:IbSKFoKW0bzmbDZ7rBwF/L3lO3b1bpmOIhTXQl/WJxw=
|
|
||||||
github.com/juju/utils v0.0.0-20180424094159-2000ea4ff043/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk=
|
|
||||||
github.com/juju/utils v0.0.0-20200116185830-d40c2fe10647/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk=
|
|
||||||
github.com/juju/utils/v2 v2.0.0-20200923005554-4646bfea2ef1/go.mod h1:fdlDtQlzundleLLz/ggoYinEt/LmnrpNKcNTABQATNI=
|
|
||||||
github.com/juju/version v0.0.0-20161031051906-1f41e27e54f2/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U=
|
|
||||||
github.com/juju/version v0.0.0-20180108022336-b64dbd566305/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U=
|
|
||||||
github.com/juju/version v0.0.0-20191219164919-81c1be00b9a6/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U=
|
|
||||||
github.com/julienschmidt/httprouter v1.1.1-0.20151013225520-77a895ad01eb/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/julienschmidt/httprouter v1.1.1-0.20151013225520-77a895ad01eb/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ=
|
github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ=
|
||||||
@ -346,8 +323,6 @@ github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 h1:o6uBwrhM5C8Ll3MAA
|
|||||||
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8=
|
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8=
|
||||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
|
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
|
||||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
|
||||||
github.com/nkovacs/streamquote v1.0.0/go.mod h1:BN+NaZ2CmdKqUuTUXUEm9j95B2TRbpOWpxbJYzzgUsc=
|
github.com/nkovacs/streamquote v1.0.0/go.mod h1:BN+NaZ2CmdKqUuTUXUEm9j95B2TRbpOWpxbJYzzgUsc=
|
||||||
github.com/nrdcg/goinwx v0.8.1 h1:20EQ/JaGFnSKwiDH2JzjIpicffl3cPk6imJBDqVBVtU=
|
github.com/nrdcg/goinwx v0.8.1 h1:20EQ/JaGFnSKwiDH2JzjIpicffl3cPk6imJBDqVBVtU=
|
||||||
github.com/nrdcg/goinwx v0.8.1/go.mod h1:tILVc10gieBp/5PMvbcYeXM6pVQ+c9jxDZnpaR1UW7c=
|
github.com/nrdcg/goinwx v0.8.1/go.mod h1:tILVc10gieBp/5PMvbcYeXM6pVQ+c9jxDZnpaR1UW7c=
|
||||||
@ -408,8 +383,6 @@ github.com/tdewolff/minify v2.3.6+incompatible h1:2hw5/9ZvxhWLvBUnHE06gElGYz+Jv9
|
|||||||
github.com/tdewolff/minify v2.3.6+incompatible/go.mod h1:9Ov578KJUmAWpS6NeZwRZyT56Uf6o3Mcz9CEsg8USYs=
|
github.com/tdewolff/minify v2.3.6+incompatible/go.mod h1:9Ov578KJUmAWpS6NeZwRZyT56Uf6o3Mcz9CEsg8USYs=
|
||||||
github.com/tdewolff/parse v2.3.4+incompatible h1:x05/cnGwIMf4ceLuDMBOdQ1qGniMoxpP46ghf0Qzh38=
|
github.com/tdewolff/parse v2.3.4+incompatible h1:x05/cnGwIMf4ceLuDMBOdQ1qGniMoxpP46ghf0Qzh38=
|
||||||
github.com/tdewolff/parse v2.3.4+incompatible/go.mod h1:8oBwCsVmUkgHO8M5iCzSIDtpzXOT0WXX9cWhz+bIzJQ=
|
github.com/tdewolff/parse v2.3.4+incompatible/go.mod h1:8oBwCsVmUkgHO8M5iCzSIDtpzXOT0WXX9cWhz+bIzJQ=
|
||||||
github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
|
|
||||||
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
|
||||||
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
||||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
@ -746,8 +719,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
|||||||
gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/errgo.v1 v1.0.0-20161222125816-442357a80af5/go.mod h1:u0ALmqvLRxLI95fkdCEWrE6mhWYZW1aMOJHp5YXLHTg=
|
gopkg.in/errgo.v1 v1.0.0-20161222125816-442357a80af5/go.mod h1:u0ALmqvLRxLI95fkdCEWrE6mhWYZW1aMOJHp5YXLHTg=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/h2non/gock.v1 v1.0.14 h1:fTeu9fcUvSnLNacYvYI54h+1/XEteDyHvrVCZEEEYNM=
|
gopkg.in/h2non/gock.v1 v1.0.14 h1:fTeu9fcUvSnLNacYvYI54h+1/XEteDyHvrVCZEEEYNM=
|
||||||
@ -756,7 +729,6 @@ gopkg.in/httprequest.v1 v1.1.1/go.mod h1:/CkavNL+g3qLOrpFHVrEx4NKepeqR4XTZWNj4sG
|
|||||||
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
|
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
|
||||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/mgo.v2 v2.0.0-20160818015218-f2b6f6c918c4/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
gopkg.in/mgo.v2 v2.0.0-20160818015218-f2b6f6c918c4/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||||
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw=
|
|
||||||
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||||
gopkg.in/ns1/ns1-go.v2 v2.4.3 h1:f7oKEJYSMD2TfR8RDeN1CT2ZsNahTPJEQfxOpmMCwvk=
|
gopkg.in/ns1/ns1-go.v2 v2.4.3 h1:f7oKEJYSMD2TfR8RDeN1CT2ZsNahTPJEQfxOpmMCwvk=
|
||||||
gopkg.in/ns1/ns1-go.v2 v2.4.3/go.mod h1:GMnKY+ZuoJ+lVLL+78uSTjwTz2jMazq6AfGKQOYhsPk=
|
gopkg.in/ns1/ns1-go.v2 v2.4.3/go.mod h1:GMnKY+ZuoJ+lVLL+78uSTjwTz2jMazq6AfGKQOYhsPk=
|
||||||
@ -767,6 +739,7 @@ gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76
|
|||||||
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
|
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
|
||||||
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637/go.mod h1:BHsqpu/nsuzkT5BpiH1EMZPLyqSMM8JbIavyFACoFNk=
|
||||||
gopkg.in/yaml.v2 v2.0.0-20170712054546-1be3d31502d6/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
gopkg.in/yaml.v2 v2.0.0-20170712054546-1be3d31502d6/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
@ -180,12 +180,17 @@ func makeChanges(t *testing.T, prv providers.DNSServiceProvider, dc *models.Doma
|
|||||||
models.PostProcessRecords(dom.Records)
|
models.PostProcessRecords(dom.Records)
|
||||||
dom2, _ := dom.Copy()
|
dom2, _ := dom.Copy()
|
||||||
|
|
||||||
|
if err := providers.AuditRecords(*providerToRun, dom.Records); err != nil {
|
||||||
|
t.Skip(fmt.Sprintf("***SKIPPED(PROVIDER DOES NOT SUPPORT '%s' ::%q)", err, desc))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// get and run corrections for first time
|
// get and run corrections for first time
|
||||||
corrections, err := prv.GetDomainCorrections(dom)
|
corrections, err := prv.GetDomainCorrections(dom)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(fmt.Errorf("runTests: %w", err))
|
t.Fatal(fmt.Errorf("runTests: %w", err))
|
||||||
}
|
}
|
||||||
if len(corrections) == 0 && expectChanges {
|
if (len(corrections) == 0 && expectChanges) && (tst.Desc != "Empty") {
|
||||||
t.Fatalf("Expected changes, but got none")
|
t.Fatalf("Expected changes, but got none")
|
||||||
}
|
}
|
||||||
for _, c := range corrections {
|
for _, c := range corrections {
|
||||||
@ -386,64 +391,49 @@ func ptr(name, target string) *models.RecordConfig {
|
|||||||
|
|
||||||
func naptr(name string, order uint16, preference uint16, flags string, service string, regexp string, target string) *models.RecordConfig {
|
func naptr(name string, order uint16, preference uint16, flags string, service string, regexp string, target string) *models.RecordConfig {
|
||||||
r := makeRec(name, target, "NAPTR")
|
r := makeRec(name, target, "NAPTR")
|
||||||
r.NaptrOrder = order
|
r.SetTargetNAPTR(order, preference, flags, service, regexp, target)
|
||||||
r.NaptrPreference = preference
|
|
||||||
r.NaptrFlags = flags
|
|
||||||
r.NaptrService = service
|
|
||||||
r.NaptrRegexp = regexp
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func ds(name string, keyTag uint16, algorithm, digestType uint8, digest string) *models.RecordConfig {
|
func ds(name string, keyTag uint16, algorithm, digestType uint8, digest string) *models.RecordConfig {
|
||||||
r := makeRec(name, "", "DS")
|
r := makeRec(name, "", "DS")
|
||||||
r.DsKeyTag = keyTag
|
r.SetTargetDS(keyTag, algorithm, digestType, digest)
|
||||||
r.DsAlgorithm = algorithm
|
|
||||||
r.DsDigestType = digestType
|
|
||||||
r.DsDigest = digest
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func srv(name string, priority, weight, port uint16, target string) *models.RecordConfig {
|
func srv(name string, priority, weight, port uint16, target string) *models.RecordConfig {
|
||||||
r := makeRec(name, target, "SRV")
|
r := makeRec(name, target, "SRV")
|
||||||
r.SrvPriority = priority
|
r.SetTargetSRV(priority, weight, port, target)
|
||||||
r.SrvWeight = weight
|
|
||||||
r.SrvPort = port
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func sshfp(name string, algorithm uint8, fingerprint uint8, target string) *models.RecordConfig {
|
func sshfp(name string, algorithm uint8, fingerprint uint8, target string) *models.RecordConfig {
|
||||||
r := makeRec(name, target, "SSHFP")
|
r := makeRec(name, target, "SSHFP")
|
||||||
r.SshfpAlgorithm = algorithm
|
r.SetTargetSSHFP(algorithm, fingerprint, target)
|
||||||
r.SshfpFingerprint = fingerprint
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func txt(name, target string) *models.RecordConfig {
|
func txt(name, target string) *models.RecordConfig {
|
||||||
// FYI: This must match the algorithm in pkg/js/helpers.js TXT.
|
r := makeRec(name, "", "TXT")
|
||||||
r := makeRec(name, target, "TXT")
|
r.SetTargetTXT(target)
|
||||||
r.TxtStrings = []string{target}
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func txtmulti(name string, target []string) *models.RecordConfig {
|
func txtmulti(name string, target []string) *models.RecordConfig {
|
||||||
// FYI: This must match the algorithm in pkg/js/helpers.js TXT.
|
r := makeRec(name, "", "TXT")
|
||||||
r := makeRec(name, target[0], "TXT")
|
r.SetTargetTXTs(target)
|
||||||
r.TxtStrings = target
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func caa(name string, tag string, flag uint8, target string) *models.RecordConfig {
|
func caa(name string, tag string, flag uint8, target string) *models.RecordConfig {
|
||||||
r := makeRec(name, target, "CAA")
|
r := makeRec(name, target, "CAA")
|
||||||
r.CaaFlag = flag
|
r.SetTargetCAA(flag, tag, target)
|
||||||
r.CaaTag = tag
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func tlsa(name string, usage, selector, matchingtype uint8, target string) *models.RecordConfig {
|
func tlsa(name string, usage, selector, matchingtype uint8, target string) *models.RecordConfig {
|
||||||
r := makeRec(name, target, "TLSA")
|
r := makeRec(name, target, "TLSA")
|
||||||
r.TlsaUsage = usage
|
r.SetTargetTLSA(usage, selector, matchingtype, target)
|
||||||
r.TlsaSelector = selector
|
|
||||||
r.TlsaMatchingType = matchingtype
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -480,6 +470,29 @@ func ttl(r *models.RecordConfig, t uint32) *models.RecordConfig {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func gentxt(s string) *TestCase {
|
||||||
|
title := fmt.Sprintf("Create TXT %s", s)
|
||||||
|
label := fmt.Sprintf("foo%d", len(s))
|
||||||
|
l := []string{}
|
||||||
|
for _, j := range s {
|
||||||
|
switch j {
|
||||||
|
case '0', 's':
|
||||||
|
//title += " short"
|
||||||
|
label += "s"
|
||||||
|
l = append(l, "short")
|
||||||
|
case 'h':
|
||||||
|
//title += " 128"
|
||||||
|
label += "h"
|
||||||
|
l = append(l, strings.Repeat("H", 128))
|
||||||
|
case '1', 'l':
|
||||||
|
//title += " 255"
|
||||||
|
label += "l"
|
||||||
|
l = append(l, strings.Repeat("Z", 255))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tc(title, txtmulti(label, l))
|
||||||
|
}
|
||||||
|
|
||||||
func manyA(namePattern, target string, n int) []*models.RecordConfig {
|
func manyA(namePattern, target string, n int) []*models.RecordConfig {
|
||||||
recs := []*models.RecordConfig{}
|
recs := []*models.RecordConfig{}
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
@ -684,10 +697,11 @@ func makeTests(t *testing.T) []*TestGroup {
|
|||||||
),
|
),
|
||||||
|
|
||||||
testgroup("NS",
|
testgroup("NS",
|
||||||
not("DNSIMPLE", "EXOSCALE", "NETCUP"),
|
not(
|
||||||
// DNSIMPLE: Does not support NS records nor subdomains.
|
"DNSIMPLE", // Does not support NS records nor subdomains.
|
||||||
// EXOSCALE: FILL IN
|
"EXOSCALE", // Not supported.
|
||||||
// Netcup: NS records not currently supported.
|
"NETCUP", // NS records not currently supported.
|
||||||
|
),
|
||||||
tc("NS for subdomain", ns("xyz", "ns2.foo.com.")),
|
tc("NS for subdomain", ns("xyz", "ns2.foo.com.")),
|
||||||
tc("Dual NS for subdomain", ns("xyz", "ns2.foo.com."), ns("xyz", "ns1.foo.com.")),
|
tc("Dual NS for subdomain", ns("xyz", "ns2.foo.com."), ns("xyz", "ns1.foo.com.")),
|
||||||
tc("NS Record pointing to @", a("@", "1.2.3.4"), ns("foo", "**current-domain**")),
|
tc("NS Record pointing to @", a("@", "1.2.3.4"), ns("foo", "**current-domain**")),
|
||||||
@ -709,57 +723,142 @@ func makeTests(t *testing.T) []*TestGroup {
|
|||||||
tc("Add a new record - ignoring **.foo.com. targets", a("bar", "1.2.3.4"), ignoreTarget("**.foo.com.", "CNAME")),
|
tc("Add a new record - ignoring **.foo.com. targets", a("bar", "1.2.3.4"), ignoreTarget("**.foo.com.", "CNAME")),
|
||||||
),
|
),
|
||||||
|
|
||||||
testgroup("single TXT",
|
testgroup("simple TXT",
|
||||||
tc("Create a TXT", txt("foo", "simple")),
|
tc("Create a TXT", txt("foo", "simple")),
|
||||||
tc("Change a TXT", txt("foo", "changed")),
|
tc("Change a TXT", txt("foo", "changed")),
|
||||||
clear(),
|
|
||||||
tc("Create a TXT with spaces", txt("foo", "with spaces")),
|
tc("Create a TXT with spaces", txt("foo", "with spaces")),
|
||||||
tc("Create 1 TXT as array", txtmulti("foo", []string{"simple"})), // Same as non-TXTMulti
|
|
||||||
clear(),
|
|
||||||
tc("Create a 254-byte TXT", txt("foo", strings.Repeat("A", 254))),
|
|
||||||
),
|
),
|
||||||
|
|
||||||
testgroup("max-sized TXT",
|
testgroup("long TXT",
|
||||||
not(
|
tc("Create long TXT", txt("foo", strings.Repeat("A", 300))),
|
||||||
"INWX", // INWX does not support
|
tc("Change long TXT", txt("foo", strings.Repeat("B", 310))),
|
||||||
"MSDNS", // Supports 254-byte strings, not 255. Seems like a bug.
|
tc("Create long TXT with spaces", txt("foo", strings.Repeat("X", 200)+" "+strings.Repeat("Y", 200))),
|
||||||
),
|
|
||||||
tc("Create a 255-byte TXT", txt("foo", strings.Repeat("A", 255))),
|
|
||||||
),
|
),
|
||||||
|
|
||||||
testgroup("single TXT with single-quote",
|
// In this next section we test all the edge cases related to TXT
|
||||||
not(
|
// records. Compliance with the RFCs varies greatly with each provider.
|
||||||
"INWX", // Bug in the API prevents this.
|
// Rather than creating a "Capability" for each possible different
|
||||||
"MSDNS", // TODO(tlim): Should be easy to implement.
|
// failing or malcompliance (there would be many!), each provider
|
||||||
"CLOUDNS", // support txt("foo", "blah'blah") but does not support txt("foo","blah`blah")
|
// supplies a function AuditRecords() which returns an error if
|
||||||
),
|
// the provider can not support a record.
|
||||||
tc("Create TXT with single-quote", txt("foo", "blah`blah")),
|
// The integration tests use this feedback to skip tests that we know would fail.
|
||||||
),
|
// (Elsewhere the result of AuditRecords() is used in the
|
||||||
|
// "dnscontrol check" phase.)
|
||||||
|
|
||||||
testgroup("ws TXT",
|
testgroup("complex TXT",
|
||||||
not("CLOUDFLAREAPI", "HEXONET", "INWX", "NAMEDOTCOM", "CLOUDNS"),
|
// Do not use only()/not()/requires() in this section.
|
||||||
// These providers strip whitespace at the end of TXT records.
|
// If your provider needs to skip one of these tests, update
|
||||||
// TODO(tal): Add a check for this in normalize/validate.go
|
// "provider/*/recordaudit.AuditRecords()" to reject that kind
|
||||||
tc("Change a TXT with ws at end", txt("foo", "with space at end ")),
|
// of record. When the provider fixes the bug or changes behavior,
|
||||||
),
|
// update the AuditRecords().
|
||||||
|
tc("TXT with 0-octel string", txt("foo1", "")),
|
||||||
testgroup("empty TXT",
|
|
||||||
not(
|
|
||||||
"HETZNER", // Not supported.
|
|
||||||
"HEXONET", // Not supported.
|
|
||||||
"INWX", // Not supported.
|
|
||||||
"MSDNS", // Not supported.
|
|
||||||
"NETCUP", // Not supported.
|
|
||||||
"CLOUDNS", // Not supported.
|
|
||||||
),
|
|
||||||
tc("TXT with empty str", txt("foo1", "")),
|
|
||||||
// https://github.com/StackExchange/dnscontrol/issues/598
|
// https://github.com/StackExchange/dnscontrol/issues/598
|
||||||
// We decided that permitting the TXT target to be an empty
|
// RFC1035 permits this, but rarely do provider support it.
|
||||||
// string is not a requirement (even though RFC1035 permits it).
|
clear(),
|
||||||
// In the future we might make it "capability" to
|
tc("Create a 253-byte TXT", txt("foo253", strings.Repeat("A", 253))),
|
||||||
// indicate which vendors support an empty TXT record.
|
clear(),
|
||||||
// However at this time there is no pressing need for this
|
tc("Create a 254-byte TXT", txt("foo254", strings.Repeat("B", 254))),
|
||||||
// feature.
|
clear(),
|
||||||
|
tc("Create a 255-byte TXT", txt("foo255", strings.Repeat("C", 255))),
|
||||||
|
clear(),
|
||||||
|
tc("Create a 256-byte TXT", txt("foo256", strings.Repeat("D", 256))),
|
||||||
|
clear(),
|
||||||
|
tc("Create a 257-byte TXT", txt("foo257", strings.Repeat("E", 257))),
|
||||||
|
clear(),
|
||||||
|
tc("Create TXT with single-quote", txt("foosq", "quo'te")),
|
||||||
|
clear(),
|
||||||
|
tc("Create TXT with backtick", txt("foobt", "blah`blah")),
|
||||||
|
clear(),
|
||||||
|
tc("Create TXT with double-quote", txt("foodq", `quo"te`)),
|
||||||
|
clear(),
|
||||||
|
tc("Create TXT with ws at end", txt("foows1", "with space at end ")),
|
||||||
|
clear(), gentxt("0"),
|
||||||
|
clear(), gentxt("1"),
|
||||||
|
clear(), gentxt("10"),
|
||||||
|
clear(), gentxt("11"),
|
||||||
|
clear(), gentxt("100"),
|
||||||
|
clear(), gentxt("101"),
|
||||||
|
clear(), gentxt("110"),
|
||||||
|
clear(), gentxt("111"),
|
||||||
|
clear(), gentxt("1hh"),
|
||||||
|
clear(), gentxt("1hh0"),
|
||||||
|
),
|
||||||
|
|
||||||
|
testgroup("long TXT",
|
||||||
|
tc("Create a 505 TXT", txt("foo257", strings.Repeat("E", 505))),
|
||||||
|
tc("Create a 506 TXT", txt("foo257", strings.Repeat("E", 506))),
|
||||||
|
tc("Create a 507 TXT", txt("foo257", strings.Repeat("E", 507))),
|
||||||
|
tc("Create a 508 TXT", txt("foo257", strings.Repeat("E", 508))),
|
||||||
|
tc("Create a 509 TXT", txt("foo257", strings.Repeat("E", 509))),
|
||||||
|
tc("Create a 510 TXT", txt("foo257", strings.Repeat("E", 510))),
|
||||||
|
tc("Create a 511 TXT", txt("foo257", strings.Repeat("E", 511))),
|
||||||
|
tc("Create a 512 TXT", txt("foo257", strings.Repeat("E", 512))),
|
||||||
|
tc("Create a 513 TXT", txt("foo257", strings.Repeat("E", 513))),
|
||||||
|
tc("Create a 514 TXT", txt("foo257", strings.Repeat("E", 514))),
|
||||||
|
tc("Create a 515 TXT", txt("foo257", strings.Repeat("E", 515))),
|
||||||
|
tc("Create a 516 TXT", txt("foo257", strings.Repeat("E", 516))),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Test the ability to change TXT records on the DIFFERENT labels accurately.
|
||||||
|
testgroup("TXTMulti",
|
||||||
|
tc("Create TXTMulti 1",
|
||||||
|
txtmulti("foo1", []string{"simple"}),
|
||||||
|
),
|
||||||
|
tc("Add TXTMulti 2",
|
||||||
|
txtmulti("foo1", []string{"simple"}),
|
||||||
|
txtmulti("foo2", []string{"one", "two"}),
|
||||||
|
),
|
||||||
|
tc("Add TXTMulti 3",
|
||||||
|
txtmulti("foo1", []string{"simple"}),
|
||||||
|
txtmulti("foo2", []string{"one", "two"}),
|
||||||
|
txtmulti("foo3", []string{"eh", "bee", "cee"}),
|
||||||
|
),
|
||||||
|
tc("Change TXTMultii-0",
|
||||||
|
txtmulti("foo1", []string{"dimple"}),
|
||||||
|
txtmulti("foo2", []string{"fun", "two"}),
|
||||||
|
txtmulti("foo3", []string{"eh", "bzz", "cee"}),
|
||||||
|
),
|
||||||
|
tc("Change TXTMulti-1[0]",
|
||||||
|
txtmulti("foo1", []string{"dimple"}),
|
||||||
|
txtmulti("foo2", []string{"moja", "two"}),
|
||||||
|
txtmulti("foo3", []string{"eh", "bzz", "cee"}),
|
||||||
|
),
|
||||||
|
tc("Change TXTMulti-1[1]",
|
||||||
|
txtmulti("foo1", []string{"dimple"}),
|
||||||
|
txtmulti("foo2", []string{"moja", "mbili"}),
|
||||||
|
txtmulti("foo3", []string{"eh", "bzz", "cee"}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Test the ability to change TXT records on the SAME labels accurately.
|
||||||
|
testgroup("TXTMulti",
|
||||||
|
tc("Create TXTMulti 1",
|
||||||
|
txtmulti("foo", []string{"simple"}),
|
||||||
|
),
|
||||||
|
tc("Add TXTMulti 2",
|
||||||
|
txtmulti("foo", []string{"simple"}),
|
||||||
|
txtmulti("foo", []string{"one", "two"}),
|
||||||
|
),
|
||||||
|
tc("Add TXTMulti 3",
|
||||||
|
txtmulti("foo", []string{"simple"}),
|
||||||
|
txtmulti("foo", []string{"one", "two"}),
|
||||||
|
txtmulti("foo", []string{"eh", "bee", "cee"}),
|
||||||
|
),
|
||||||
|
tc("Change TXTMultii-0",
|
||||||
|
txtmulti("foo", []string{"dimple"}),
|
||||||
|
txtmulti("foo", []string{"fun", "two"}),
|
||||||
|
txtmulti("foo", []string{"eh", "bzz", "cee"}),
|
||||||
|
),
|
||||||
|
tc("Change TXTMulti-1[0]",
|
||||||
|
txtmulti("foo", []string{"dimple"}),
|
||||||
|
txtmulti("foo", []string{"moja", "two"}),
|
||||||
|
txtmulti("foo", []string{"eh", "bzz", "cee"}),
|
||||||
|
),
|
||||||
|
tc("Change TXTMulti-1[1]",
|
||||||
|
txtmulti("foo", []string{"dimple"}),
|
||||||
|
txtmulti("foo", []string{"moja", "mbili"}),
|
||||||
|
txtmulti("foo", []string{"eh", "bzz", "cee"}),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -920,69 +1019,6 @@ func makeTests(t *testing.T) []*TestGroup {
|
|||||||
tc("TLSA change certificate", tlsa("_443._tcp", 2, 0, 2, reversedSha512)),
|
tc("TLSA change certificate", tlsa("_443._tcp", 2, 0, 2, reversedSha512)),
|
||||||
),
|
),
|
||||||
|
|
||||||
testgroup("TXTMulti",
|
|
||||||
not("CLOUDNS"), //TODO: not implemented. same Issue as #996
|
|
||||||
requires(providers.CanUseTXTMulti),
|
|
||||||
tc("Create TXTMulti 1",
|
|
||||||
txtmulti("foo1", []string{"simple"}),
|
|
||||||
),
|
|
||||||
tc("Create TXTMulti 2",
|
|
||||||
txtmulti("foo1", []string{"simple"}),
|
|
||||||
txtmulti("foo2", []string{"one", "two"}),
|
|
||||||
),
|
|
||||||
tc("Create TXTMulti 3",
|
|
||||||
txtmulti("foo1", []string{"simple"}),
|
|
||||||
txtmulti("foo2", []string{"one", "two"}),
|
|
||||||
txtmulti("foo3", []string{"eh", "bee", "cee"}),
|
|
||||||
),
|
|
||||||
tc("Change TXTMulti",
|
|
||||||
txtmulti("foo1", []string{"dimple"}),
|
|
||||||
txtmulti("foo2", []string{"fun", "two"}),
|
|
||||||
txtmulti("foo3", []string{"eh", "bzz", "cee"}),
|
|
||||||
),
|
|
||||||
tc("Long TXTMulti",
|
|
||||||
// This isn't the longest a TXT record can be, but it is the
|
|
||||||
// longest DIGITALOCEAN permits.
|
|
||||||
txtmulti("foo4", []string{strings.Repeat("X", 255), strings.Repeat("Y", 252)}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
testgroup("TXTMulti tests that break DO",
|
|
||||||
// DO's implementation of TXTMulti is broken, thus we separate out
|
|
||||||
// tests that fail for it. Users are warned about these limits
|
|
||||||
// in docs/_providers/digitalocean.md
|
|
||||||
requires(providers.CanUseTXTMulti),
|
|
||||||
not("DIGITALOCEAN"),
|
|
||||||
// Digital Ocean's TXT record implementation checks size limits wrong.
|
|
||||||
// RFC 1035 Section 3.3.14 states that each substring can be 255
|
|
||||||
// octets, and there is no limit on the number of such
|
|
||||||
// substrings, aside from the usual packet length limits. DO's
|
|
||||||
// implementation restricts the total length to be 512 octets,
|
|
||||||
// including any backlashes used for escapes, quotes, and other
|
|
||||||
// metachars. In other words, they're doing the checking on the
|
|
||||||
// API protocol encoded data instead of on on the resulting TXT
|
|
||||||
// record.
|
|
||||||
// Proper TXT implementations can handle TXT records like this:
|
|
||||||
tc("3x255-byte TXTMulti",
|
|
||||||
txtmulti("foo3", []string{strings.Repeat("X", 255), strings.Repeat("Y", 255), strings.Repeat("Z", 255)})),
|
|
||||||
clear(),
|
|
||||||
// Digital Ocean's TXT record implementation handles quotes wrong.
|
|
||||||
// It craps out if your TXT record includes double-quotes.
|
|
||||||
// Someone doesn't understand how zonefile-style quoting is
|
|
||||||
// supposed to work.
|
|
||||||
// Proper TXT implementations can handle TXT records like these:
|
|
||||||
tc("Create TXTMulti with quotes",
|
|
||||||
txtmulti("foo1", []string{"simple"}),
|
|
||||||
txtmulti("foo2", []string{"o\"ne", "tw\"o"}),
|
|
||||||
txtmulti("foo3", []string{"eh", "bee", "cee"}),
|
|
||||||
),
|
|
||||||
tc("Change TXTMulti",
|
|
||||||
txtmulti("foo1", []string{"dimple"}),
|
|
||||||
txtmulti("foo2", []string{"fun", "t\"wo"}),
|
|
||||||
txtmulti("foo3", []string{"eh", "bzz", "cee"}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
testgroup("DS",
|
testgroup("DS",
|
||||||
requires(providers.CanUseDS),
|
requires(providers.CanUseDS),
|
||||||
tc("create DS", ds("@", 1, 13, 1, "ADIGEST")),
|
tc("create DS", ds("@", 1, 13, 1, "ADIGEST")),
|
||||||
|
@ -1,112 +0,0 @@
|
|||||||
--- PASS: TestDNSProviders/example.com/0:_Empty (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/1:_Create_an_A_record (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/2:_Change_it (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/3:_Add_another (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/4:_Add_another(same_name) (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/5:_Change_a_ttl (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/6:_Change_single_target_from_set (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/7:_Change_all_ttls (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/8:_Delete_one (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/9:_Add_back_and_change_ttl (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/10:_Change_targets_and_ttls (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/11:_Create_wildcard (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/12:_Delete_wildcard (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/13:_Empty (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/14:_Create_a_CNAME (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/15:_Change_it (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/16:_Change_to_A_record (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/17:_Change_back_to_CNAME (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/18:_Record_pointing_to_@ (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/19:_Empty (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/20:_NS_for_subdomain (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/21:_Dual_NS_for_subdomain (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/22:_NS_Record_pointing_to_@ (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/23:_Empty (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/24:_Internationalized_name (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/25:_Change_IDN (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/26:_Internationalized_CNAME_Target (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/27:_IDN_CNAME_AND_Target (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/28:_Empty (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/29:_MX_record (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/30:_Second_MX_record,_same_prio (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/31:_3_MX (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/32:_Delete_one (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/33:_Change_to_other_name (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/34:_Change_Preference (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/35:_Record_pointing_to_@ (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/36:_Empty (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/37:_Create_PTR_record (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/38:_Modify_PTR_record (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/39:_Empty (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/40:_NAPTR_record (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/41:_NAPTR_second_record (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/42:_NAPTR_delete_record (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/43:_NAPTR_change_target (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/44:_NAPTR_change_order (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/45:_NAPTR_change_preference (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/46:_NAPTR_change_flags (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/47:_NAPTR_change_service (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/48:_NAPTR_change_regexp (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/49:_Empty (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/50:_SRV_record (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/51:_Second_SRV_record,_same_prio (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/52:_3_SRV (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/53:_Delete_one (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/54:_Change_Target (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/55:_Change_Priority (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/56:_Change_Weight (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/57:_Change_Port (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/58:_Null_Target (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/59:_Empty (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/60:_SSHFP_record (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/61:_SSHFP_change_algorithm (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/62:_SSHFP_change_fingerprint_and_type (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/63:_SSHFP_Delete_one (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/64:_SSHFP_add_many_records (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/65:_SSHFP_delete_two (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/66:_Empty (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/67:_CAA_record (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/68:_CAA_change_tag (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/69:_CAA_change_target (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/70:_CAA_change_flag (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/71:_CAA_many_records (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/72:_CAA_delete (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/73:_Empty (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/74:_TLSA_record (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/75:_TLSA_change_usage (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/76:_TLSA_change_selector (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/77:_TLSA_change_matchingtype (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/78:_TLSA_change_certificate (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/79:_Empty (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/80:_Create_CAPS (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/81:_Downcase_label (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/82:_Downcase_target (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/83:_Upcase_both (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/84:_Empty (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/85:_99_records (0.01s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/86:_100_records (0.01s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/87:_101_records (0.03s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/88:_Empty (0.01s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/89:_Create_a_TXT (0.03s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/90:_Change_a_TXT (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/91:_Empty (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/92:_Create_a_TXT_with_spaces (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/93:_Change_a_TXT_with_spaces (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/94:_Create_1_TXT_as_array (0.01s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/95:_Empty (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/96:_Create_a_255-byte_TXT (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/97:_Empty (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/98:_Create_TXTMulti_1 (0.01s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/99:_Create_TXTMulti_2 (0.01s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/100:_Create_TXTMulti_3 (0.02s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/101:_Create_TXTMulti_with_quotes (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/102:_Change_TXTMulti (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/103:_Empty (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/104:_3x255-byte_TXTMulti (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/105:_Empty (0.01s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/106:_Create_some_records (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/107:_Add_a_new_record_-_ignoring_foo (0.02s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/108:_Empty (0.01s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/109:_Create_some_records (0.01s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/110:_Add_a_new_record_-_ignoring_*.foo (0.00s)
|
|
||||||
--- PASS: TestDNSProviders/example.com/111:_Empty (0.01s)
|
|
@ -16,6 +16,7 @@ type DNSConfig struct {
|
|||||||
Domains []*DomainConfig `json:"domains"`
|
Domains []*DomainConfig `json:"domains"`
|
||||||
RegistrarsByName map[string]*RegistrarConfig `json:"-"`
|
RegistrarsByName map[string]*RegistrarConfig `json:"-"`
|
||||||
DNSProvidersByName map[string]*DNSProviderConfig `json:"-"`
|
DNSProvidersByName map[string]*DNSProviderConfig `json:"-"`
|
||||||
|
SkipRecordAudit bool `json:"skiprecordaudit,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindDomain returns the *DomainConfig for domain query in config.
|
// FindDomain returns the *DomainConfig for domain query in config.
|
||||||
|
@ -108,7 +108,7 @@ type RecordConfig struct {
|
|||||||
TlsaUsage uint8 `json:"tlsausage,omitempty"`
|
TlsaUsage uint8 `json:"tlsausage,omitempty"`
|
||||||
TlsaSelector uint8 `json:"tlsaselector,omitempty"`
|
TlsaSelector uint8 `json:"tlsaselector,omitempty"`
|
||||||
TlsaMatchingType uint8 `json:"tlsamatchingtype,omitempty"`
|
TlsaMatchingType uint8 `json:"tlsamatchingtype,omitempty"`
|
||||||
TxtStrings []string `json:"txtstrings,omitempty"` // TxtStrings stores all strings (including the first). Target stores only the first one.
|
TxtStrings []string `json:"txtstrings,omitempty"` // TxtStrings stores all strings (including the first). Target stores all the strings joined.
|
||||||
R53Alias map[string]string `json:"r53_alias,omitempty"`
|
R53Alias map[string]string `json:"r53_alias,omitempty"`
|
||||||
AzureAlias map[string]string `json:"azure_alias,omitempty"`
|
AzureAlias map[string]string `json:"azure_alias,omitempty"`
|
||||||
}
|
}
|
||||||
|
108
models/t_txt.go
108
models/t_txt.go
@ -1,45 +1,73 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
/*
|
||||||
"fmt"
|
Sadly many providers handle TXT records in strange and non-compliant ways.
|
||||||
"strings"
|
|
||||||
)
|
The variations we've seen:
|
||||||
|
|
||||||
|
* a TXT record is a list of strings, each 255-octets or fewer.
|
||||||
|
* a TXT record is a list of strings, all but the last will be 255 octets in length.
|
||||||
|
* a TXT record is a string less than 256 octets.
|
||||||
|
* a TXT record is any length, but we'll split it into 255-octet chunks behind the scenes.
|
||||||
|
|
||||||
|
DNSControl stores the string as the user specified, then lets the
|
||||||
|
provider work out how to handle the given input. There are two
|
||||||
|
opportunties to work with the data:
|
||||||
|
|
||||||
|
1. The provider's AuditRecords() function is called to permit
|
||||||
|
the provider to return an error if it won't be able to handle the
|
||||||
|
contents. For example, it might detect that the string contains a char
|
||||||
|
the provider doesn't support (for example, a backtick). This auditing
|
||||||
|
is done without any communication to the provider's API. This allows
|
||||||
|
such errors to be detected at the "dnscontrol check" stage. 2. When
|
||||||
|
performing corrections (GetDomainCorrections()), the provider can slice
|
||||||
|
and dice the user's input however they want.
|
||||||
|
|
||||||
|
* If the user input is a list of strings:
|
||||||
|
* The strings are stored in RecordConfig.TxtStrings
|
||||||
|
* If the user input is a single string:
|
||||||
|
* The strings are stored in RecordConfig.TxtStrings[0]
|
||||||
|
|
||||||
|
In both cases, the .Target stores a string that can be used in error
|
||||||
|
messages and other UI messages. This string should not be used by the
|
||||||
|
provider in any other way because it has been modified from the user's
|
||||||
|
original input.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
// HasFormatIdenticalToTXT returns if a RecordConfig has a format which is
|
// HasFormatIdenticalToTXT returns if a RecordConfig has a format which is
|
||||||
// identical to TXT, such as SPF. For more details, read
|
// identical to TXT, such as SPF. For more details, read
|
||||||
// https://tools.ietf.org/html/rfc4408#section-3.1.1
|
// https://tools.ietf.org/html/rfc4408#section-3.1.1
|
||||||
func (rc *RecordConfig) HasFormatIdenticalToTXT() bool {
|
func (rc *RecordConfig) HasFormatIdenticalToTXT() bool {
|
||||||
switch rc.Type {
|
return rc.Type == "TXT" || rc.Type == "SPF"
|
||||||
case "SPF", "TXT":
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTargetTXT sets the TXT fields when there is 1 string.
|
// SetTargetTXT sets the TXT fields when there is 1 string.
|
||||||
|
// The string is stored in .Target, and split into 255-octet chunks
|
||||||
|
// for .TxtStrings.
|
||||||
func (rc *RecordConfig) SetTargetTXT(s string) error {
|
func (rc *RecordConfig) SetTargetTXT(s string) error {
|
||||||
rc.SetTarget(s)
|
|
||||||
rc.TxtStrings = []string{s}
|
|
||||||
if rc.Type == "" {
|
if rc.Type == "" {
|
||||||
rc.Type = "TXT"
|
rc.Type = "TXT"
|
||||||
|
} else if !rc.HasFormatIdenticalToTXT() {
|
||||||
|
panic("assertion failed: SetTargetTXT called when .Type is not TXT or compatible type")
|
||||||
}
|
}
|
||||||
if !rc.HasFormatIdenticalToTXT() {
|
|
||||||
panic("assertion failed: SetTargetTXT called when .Type is not identical to TXT")
|
rc.TxtStrings = []string{s}
|
||||||
}
|
rc.SetTarget(rc.zoneFileQuoted())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTargetTXTs sets the TXT fields when there are many strings.
|
// SetTargetTXTs sets the TXT fields when there are many strings.
|
||||||
|
// The individual strings are stored in .TxtStrings, and joined to make .Target.
|
||||||
func (rc *RecordConfig) SetTargetTXTs(s []string) error {
|
func (rc *RecordConfig) SetTargetTXTs(s []string) error {
|
||||||
rc.SetTarget(s[0])
|
|
||||||
rc.TxtStrings = s
|
|
||||||
if rc.Type == "" {
|
if rc.Type == "" {
|
||||||
rc.Type = "TXT"
|
rc.Type = "TXT"
|
||||||
|
} else if !rc.HasFormatIdenticalToTXT() {
|
||||||
|
panic("assertion failed: SetTargetTXTs called when .Type is not TXT or compatible type")
|
||||||
}
|
}
|
||||||
if !rc.HasFormatIdenticalToTXT() {
|
|
||||||
panic("assertion failed: SetTargetTXT called when .Type is not identical to TXT")
|
rc.TxtStrings = s
|
||||||
}
|
rc.SetTarget(rc.zoneFileQuoted())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,43 +79,3 @@ func (rc *RecordConfig) SetTargetTXTs(s []string) error {
|
|||||||
func (rc *RecordConfig) SetTargetTXTString(s string) error {
|
func (rc *RecordConfig) SetTargetTXTString(s string) error {
|
||||||
return rc.SetTargetTXTs(ParseQuotedTxt(s))
|
return rc.SetTargetTXTs(ParseQuotedTxt(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TxtNormalize splits long txt targets if required based on the algo.
|
|
||||||
func (rc *RecordConfig) TxtNormalize(algo string) {
|
|
||||||
switch algo {
|
|
||||||
case "multistring":
|
|
||||||
rc.SetTargetTXTs(splitChunks(strings.Join(rc.TxtStrings, ""), 255))
|
|
||||||
case "space":
|
|
||||||
panic("not implemented")
|
|
||||||
default:
|
|
||||||
panic("TxtNormalize called with invalid algorithm")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitChunks(buf string, lim int) []string {
|
|
||||||
var chunk string
|
|
||||||
chunks := make([]string, 0, len(buf)/lim+1)
|
|
||||||
for len(buf) >= lim {
|
|
||||||
chunk, buf = buf[:lim], buf[lim:]
|
|
||||||
chunks = append(chunks, chunk)
|
|
||||||
}
|
|
||||||
if len(buf) > 0 {
|
|
||||||
chunks = append(chunks, buf[:])
|
|
||||||
}
|
|
||||||
return chunks
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateTXT returns an error if the txt record is invalid.
|
|
||||||
// Verifies the Target and TxtStrings are less than 255 bytes each.
|
|
||||||
func ValidateTXT(rc *RecordConfig) error {
|
|
||||||
if !rc.HasFormatIdenticalToTXT() {
|
|
||||||
return fmt.Errorf("rc.Type=%q, expecting something identical to TXT", rc.Type)
|
|
||||||
}
|
|
||||||
for i := range rc.TxtStrings {
|
|
||||||
l := len(rc.TxtStrings[i])
|
|
||||||
if l > 255 {
|
|
||||||
return fmt.Errorf("txt target >255 bytes and AUTOSPLIT not set: label=%q index=%d len=%d string[:50]=%q", rc.GetLabel(), i, l, rc.TxtStrings[i][:50]+"...")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@ -1,104 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestValidateTXT_single(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
d1 string
|
|
||||||
e1 bool
|
|
||||||
}{
|
|
||||||
{`x`, true},
|
|
||||||
{`foo`, true},
|
|
||||||
{strings.Repeat(`A`, 253), true},
|
|
||||||
{strings.Repeat(`B`, 254), true},
|
|
||||||
{strings.Repeat(`C`, 255), true},
|
|
||||||
{strings.Repeat(`D`, 256), false},
|
|
||||||
{strings.Repeat(`E`, 257), false},
|
|
||||||
}
|
|
||||||
for i, test := range tests {
|
|
||||||
rc := &RecordConfig{Type: "TXT"}
|
|
||||||
rc.SetTargetTXT(test.d1)
|
|
||||||
r := ValidateTXT(rc)
|
|
||||||
if test.e1 != (r == nil) {
|
|
||||||
s := "<nil>"
|
|
||||||
if r == nil {
|
|
||||||
s = "error"
|
|
||||||
}
|
|
||||||
t.Errorf("%v: expected (%v) got (%v) (%q)", i, s, r, test.d1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidateTXT_multi(t *testing.T) {
|
|
||||||
s1 := "A"
|
|
||||||
s253 := strings.Repeat("C", 253)
|
|
||||||
s254 := strings.Repeat("C", 254)
|
|
||||||
s255 := strings.Repeat("D", 255)
|
|
||||||
s256 := strings.Repeat("E", 256)
|
|
||||||
s257 := strings.Repeat("E", 256)
|
|
||||||
tests := []struct {
|
|
||||||
d1 []string
|
|
||||||
e1 bool
|
|
||||||
}{
|
|
||||||
{[]string{`x`}, true},
|
|
||||||
{[]string{`foo`}, true},
|
|
||||||
{[]string{s253}, true},
|
|
||||||
{[]string{s254}, true},
|
|
||||||
{[]string{s255}, true},
|
|
||||||
{[]string{s256}, false},
|
|
||||||
{[]string{s257}, false},
|
|
||||||
|
|
||||||
{[]string{s1, s254}, true},
|
|
||||||
{[]string{s1, s255}, true},
|
|
||||||
{[]string{s1, s256}, false},
|
|
||||||
|
|
||||||
{[]string{s1, s254, s1}, true},
|
|
||||||
{[]string{s1, s255, s1}, true},
|
|
||||||
{[]string{s1, s256, s1}, false},
|
|
||||||
}
|
|
||||||
for i, test := range tests {
|
|
||||||
rc := &RecordConfig{Type: "TXT"}
|
|
||||||
rc.SetTargetTXTs(test.d1)
|
|
||||||
r := ValidateTXT(rc)
|
|
||||||
if test.e1 != (r == nil) {
|
|
||||||
s := "<nil>"
|
|
||||||
if r == nil {
|
|
||||||
s = "error"
|
|
||||||
}
|
|
||||||
t.Errorf("%v: expected (%v) got: (%v)", i, s, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_splitChunks(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
buf string
|
|
||||||
lim int
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want []string
|
|
||||||
}{
|
|
||||||
{"0", args{"", 3}, []string{}},
|
|
||||||
{"1", args{"a", 3}, []string{"a"}},
|
|
||||||
{"2", args{"ab", 3}, []string{"ab"}},
|
|
||||||
{"3", args{"abc", 3}, []string{"abc"}},
|
|
||||||
{"4", args{"abcd", 3}, []string{"abc", "d"}},
|
|
||||||
{"5", args{"abcde", 3}, []string{"abc", "de"}},
|
|
||||||
{"6", args{"abcdef", 3}, []string{"abc", "def"}},
|
|
||||||
{"7", args{"abcdefg", 3}, []string{"abc", "def", "g"}},
|
|
||||||
{"8", args{"abcdefgh", 3}, []string{"abc", "def", "gh"}},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if got := splitChunks(tt.args.buf, tt.args.lim); !reflect.DeepEqual(got, tt.want) {
|
|
||||||
t.Errorf("splitChunks() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -23,9 +23,6 @@ so that it is easy to do things the right way in preparation.
|
|||||||
// GetTargetField returns the target. There may be other fields (for example
|
// GetTargetField returns the target. There may be other fields (for example
|
||||||
// an MX record also has a .MxPreference field.
|
// an MX record also has a .MxPreference field.
|
||||||
func (rc *RecordConfig) GetTargetField() string {
|
func (rc *RecordConfig) GetTargetField() string {
|
||||||
if rc.HasFormatIdenticalToTXT() {
|
|
||||||
return strings.Join(rc.TxtStrings, "")
|
|
||||||
}
|
|
||||||
return rc.target
|
return rc.target
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,6 +63,11 @@ func (rc *RecordConfig) GetTargetCombined() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return rc.zoneFileQuoted()
|
||||||
|
}
|
||||||
|
|
||||||
|
// zoneFileQuoted returns the rData as would be quoted in a zonefile.
|
||||||
|
func (rc *RecordConfig) zoneFileQuoted() string {
|
||||||
// We cheat by converting to a dns.RR and use the String() function.
|
// We cheat by converting to a dns.RR and use the String() function.
|
||||||
// This combines all the data for us, and even does proper quoting.
|
// This combines all the data for us, and even does proper quoting.
|
||||||
// Sadly String() always includes a header, which we must strip out.
|
// Sadly String() always includes a header, which we must strip out.
|
||||||
|
@ -414,10 +414,8 @@ function isStringOrArray(x) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// AUTOSPLIT is a modifier that instructs the Go-level code to
|
// AUTOSPLIT is deprecated. It is now a no-op.
|
||||||
// split this TXT record's target into chunks of 255.
|
var AUTOSPLIT = { };
|
||||||
var AUTOSPLIT = { txtSplitAlgorithm: 'multistring' }; // Create 255-byte chunks
|
|
||||||
//var TXTMULTISPACE = { txtSplitAlgorithm: 'space' }; // Split on space [not implemented]
|
|
||||||
|
|
||||||
// TXT(name,target, recordModifiers...)
|
// TXT(name,target, recordModifiers...)
|
||||||
var TXT = recordBuilder('TXT', {
|
var TXT = recordBuilder('TXT', {
|
||||||
@ -427,20 +425,13 @@ var TXT = recordBuilder('TXT', {
|
|||||||
],
|
],
|
||||||
transform: function(record, args, modifiers) {
|
transform: function(record, args, modifiers) {
|
||||||
record.name = args.name;
|
record.name = args.name;
|
||||||
// Store the strings twice:
|
// Store the strings from the user verbatim.
|
||||||
// .target is the first string
|
|
||||||
// .txtstrings is the individual strings.
|
|
||||||
// NOTE: If there are more than 1 string, providers should only access
|
|
||||||
// .txtstrings, thus it doesn't matter what we store in .target.
|
|
||||||
// However, by storing the first string there, it improves backwards
|
|
||||||
// compatibility when the len(array) == 1 and (intentionally) breaks
|
|
||||||
// broken providers early in the integration tests.
|
|
||||||
if (_.isString(args.target)) {
|
if (_.isString(args.target)) {
|
||||||
record.target = args.target;
|
|
||||||
record.txtstrings = [args.target];
|
record.txtstrings = [args.target];
|
||||||
|
record.target = args.target; // Overwritten by the Go code
|
||||||
} else {
|
} else {
|
||||||
record.target = args.target[0];
|
|
||||||
record.txtstrings = args.target;
|
record.txtstrings = args.target;
|
||||||
|
record.target = args.target.join(""); // Overwritten by the Go code
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
250
pkg/js/static.go
250
pkg/js/static.go
@ -212,135 +212,131 @@ var _escData = map[string]*_escFile{
|
|||||||
"/helpers.js": {
|
"/helpers.js": {
|
||||||
name: "helpers.js",
|
name: "helpers.js",
|
||||||
local: "pkg/js/helpers.js",
|
local: "pkg/js/helpers.js",
|
||||||
size: 28655,
|
size: 28050,
|
||||||
modtime: 0,
|
modtime: 0,
|
||||||
compressed: `
|
compressed: `
|
||||||
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
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ func TestCapabilitiesAreFiltered(t *testing.T) {
|
|||||||
// Any capabilities which we wish to whitelist because it's not directly
|
// Any capabilities which we wish to whitelist because it's not directly
|
||||||
// something we can test against.
|
// something we can test against.
|
||||||
skipCheckCapabilities := make(map[string]struct{})
|
skipCheckCapabilities := make(map[string]struct{})
|
||||||
skipCheckCapabilities["CanUseTXTMulti"] = struct{}{}
|
//skipCheckCapabilities["CanUseBlahBlahBlah"] = struct{}{}
|
||||||
|
|
||||||
fset := token.NewFileSet()
|
fset := token.NewFileSet()
|
||||||
pkgs, err := parser.ParseDir(fset, providersImportDir, nil, 0)
|
pkgs, err := parser.ParseDir(fset, providersImportDir, nil, 0)
|
||||||
|
@ -85,9 +85,6 @@ func validateRecordTypes(rec *models.RecordConfig, domain string, pTypes []strin
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// these record types may contain underscores
|
|
||||||
var rTypeUnderscores = []string{"SRV", "TLSA", "TXT"}
|
|
||||||
|
|
||||||
func checkLabel(label string, rType string, target, domain string, meta map[string]string) error {
|
func checkLabel(label string, rType string, target, domain string, meta map[string]string) error {
|
||||||
if label == "@" {
|
if label == "@" {
|
||||||
return nil
|
return nil
|
||||||
@ -108,7 +105,7 @@ func checkLabel(label string, rType string, target, domain string, meta map[stri
|
|||||||
// are used in a way we consider typical. Yes, we're opinionated here.
|
// are used in a way we consider typical. Yes, we're opinionated here.
|
||||||
|
|
||||||
// Don't warn for certain rtypes:
|
// Don't warn for certain rtypes:
|
||||||
for _, ex := range rTypeUnderscores {
|
for _, ex := range []string{"SRV", "TLSA", "TXT"} {
|
||||||
if rType == ex {
|
if rType == ex {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -179,6 +176,17 @@ func checkTargets(rec *models.RecordConfig, domain string) (errs []error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Write a test.
|
||||||
|
func checkTxtStrings(rc *models.RecordConfig) error {
|
||||||
|
for i := range rc.TxtStrings {
|
||||||
|
l := len([]byte(rc.TxtStrings[i]))
|
||||||
|
if l > 255 {
|
||||||
|
return fmt.Errorf("length of TxtStrings[%d] is %d, which is >255", i, l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func transformCNAME(target, oldDomain, newDomain string) string {
|
func transformCNAME(target, oldDomain, newDomain string) string {
|
||||||
// Canonicalize. If it isn't a FQDN, add the newDomain.
|
// Canonicalize. If it isn't a FQDN, add the newDomain.
|
||||||
result := dnsutil.AddOrigin(target, oldDomain)
|
result := dnsutil.AddOrigin(target, oldDomain)
|
||||||
@ -319,6 +327,11 @@ func ValidateAndNormalizeConfig(config *models.DNSConfig) (errs []error) {
|
|||||||
if errs2 := checkTargets(rec, domain.Name); errs2 != nil {
|
if errs2 := checkTargets(rec, domain.Name); errs2 != nil {
|
||||||
errs = append(errs, errs2...)
|
errs = append(errs, errs2...)
|
||||||
}
|
}
|
||||||
|
if rec.HasFormatIdenticalToTXT() { // i.e. if it is a TXT or SPF record.
|
||||||
|
if err := checkTxtStrings(rec); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Canonicalize Targets.
|
// Canonicalize Targets.
|
||||||
if rec.Type == "CNAME" || rec.Type == "MX" || rec.Type == "NAPTR" || rec.Type == "NS" || rec.Type == "SRV" {
|
if rec.Type == "CNAME" || rec.Type == "MX" || rec.Type == "NAPTR" || rec.Type == "NS" || rec.Type == "SRV" {
|
||||||
@ -369,44 +382,6 @@ func ValidateAndNormalizeConfig(config *models.DNSConfig) (errs []error) {
|
|||||||
errs = append(errs, ers...)
|
errs = append(errs, ers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split TXT targets that are >255 bytes (if permitted)
|
|
||||||
for _, domain := range config.Domains {
|
|
||||||
for _, rec := range domain.Records {
|
|
||||||
if rec.HasFormatIdenticalToTXT() {
|
|
||||||
if txtAlgo, ok := rec.Metadata["txtSplitAlgorithm"]; ok {
|
|
||||||
rec.TxtNormalize(txtAlgo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate TXT records.
|
|
||||||
for _, domain := range config.Domains {
|
|
||||||
// Collect the names of providers that don't support TXTMulti:
|
|
||||||
txtMultiDissenters := []string{}
|
|
||||||
for _, provider := range domain.DNSProviderInstances {
|
|
||||||
pType := provider.ProviderType
|
|
||||||
if !providers.ProviderHasCapability(pType, providers.CanUseTXTMulti) {
|
|
||||||
txtMultiDissenters = append(txtMultiDissenters, provider.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Validate TXT records.
|
|
||||||
for _, rec := range domain.Records {
|
|
||||||
if rec.HasFormatIdenticalToTXT() {
|
|
||||||
// If TXTMulti is required, all providers must support that feature.
|
|
||||||
if len(rec.TxtStrings) > 1 && len(txtMultiDissenters) > 0 {
|
|
||||||
errs = append(errs,
|
|
||||||
fmt.Errorf("%s records with multiple strings not supported by %s (label=%q domain=%v)",
|
|
||||||
rec.Type, strings.Join(txtMultiDissenters, ","), rec.GetLabel(), domain.Name))
|
|
||||||
}
|
|
||||||
// Validate the record:
|
|
||||||
if err := models.ValidateTXT(rec); err != nil {
|
|
||||||
errs = append(errs, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process IMPORT_TRANSFORM
|
// Process IMPORT_TRANSFORM
|
||||||
for _, domain := range config.Domains {
|
for _, domain := range config.Domains {
|
||||||
for _, rec := range domain.Records {
|
for _, rec := range domain.Records {
|
||||||
@ -459,6 +434,17 @@ func ValidateAndNormalizeConfig(config *models.DNSConfig) (errs []error) {
|
|||||||
errs = append(errs, checkAutoDNSSEC(d)...)
|
errs = append(errs, checkAutoDNSSEC(d)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// At this point we've munged anything that needs to be munged, and
|
||||||
|
// validated anything that can be globally validated.
|
||||||
|
// Let's ask // the provider if there are any records they can't handle.
|
||||||
|
for _, domain := range config.Domains { // For each domain..
|
||||||
|
for _, provider := range domain.DNSProviderInstances { // For each provider...
|
||||||
|
if err := providers.AuditRecords(provider.ProviderBase.ProviderType, domain.Records); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,14 +293,14 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
providers.RegisterDomainServiceProviderType(ProviderNoDS, nil, providers.DocumentationNotes{})
|
providers.RegisterDomainServiceProviderType(ProviderNoDS, providers.DspFuncs{}, providers.DocumentationNotes{})
|
||||||
providers.RegisterDomainServiceProviderType(ProviderFullDS, nil, providers.DocumentationNotes{
|
providers.RegisterDomainServiceProviderType(ProviderFullDS, providers.DspFuncs{}, providers.DocumentationNotes{
|
||||||
providers.CanUseDS: providers.Can(),
|
providers.CanUseDS: providers.Can(),
|
||||||
})
|
})
|
||||||
providers.RegisterDomainServiceProviderType(ProviderChildDSOnly, nil, providers.DocumentationNotes{
|
providers.RegisterDomainServiceProviderType(ProviderChildDSOnly, providers.DspFuncs{}, providers.DocumentationNotes{
|
||||||
providers.CanUseDSForChildren: providers.Can(),
|
providers.CanUseDSForChildren: providers.Can(),
|
||||||
})
|
})
|
||||||
providers.RegisterDomainServiceProviderType(ProviderBothDSCaps, nil, providers.DocumentationNotes{
|
providers.RegisterDomainServiceProviderType(ProviderBothDSCaps, providers.DspFuncs{}, providers.DocumentationNotes{
|
||||||
providers.CanUseDS: providers.Can(),
|
providers.CanUseDS: providers.Can(),
|
||||||
providers.CanUseDSForChildren: providers.Can(),
|
providers.CanUseDSForChildren: providers.Can(),
|
||||||
})
|
})
|
||||||
|
144
pkg/recordaudit/txt.go
Normal file
144
pkg/recordaudit/txt.go
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
package recordaudit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Keep these in alphabetical order.
|
||||||
|
|
||||||
|
// TxtNoBackticks audits TXT records for strings that contain backticks.
|
||||||
|
func TxtNoBackticks(records []*models.RecordConfig) error {
|
||||||
|
for _, rc := range records {
|
||||||
|
|
||||||
|
if rc.HasFormatIdenticalToTXT() {
|
||||||
|
for _, txt := range rc.TxtStrings {
|
||||||
|
if strings.Index(txt, "`") != -1 {
|
||||||
|
return fmt.Errorf("txtstring contains backtick")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxtNoSingleQuotes audits TXT records for strings that contain single-quotes.
|
||||||
|
func TxtNoSingleQuotes(records []*models.RecordConfig) error {
|
||||||
|
for _, rc := range records {
|
||||||
|
|
||||||
|
if rc.HasFormatIdenticalToTXT() {
|
||||||
|
for _, txt := range rc.TxtStrings {
|
||||||
|
if strings.Index(txt, "'") != -1 {
|
||||||
|
return fmt.Errorf("txtstring contains single-quotes")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxtNoDoubleQuotes audits TXT records for strings that contain doublequotes.
|
||||||
|
func TxtNoDoubleQuotes(records []*models.RecordConfig) error {
|
||||||
|
for _, rc := range records {
|
||||||
|
|
||||||
|
if rc.HasFormatIdenticalToTXT() {
|
||||||
|
for _, txt := range rc.TxtStrings {
|
||||||
|
if strings.Index(txt, `"`) != -1 {
|
||||||
|
return fmt.Errorf("txtstring contains doublequotes")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxtNoLen255 audits TXT records for strings exactly 255 octets long.
|
||||||
|
func TxtNoLen255(records []*models.RecordConfig) error {
|
||||||
|
for _, rc := range records {
|
||||||
|
|
||||||
|
if rc.HasFormatIdenticalToTXT() { // TXT and similar:
|
||||||
|
for _, txt := range rc.TxtStrings {
|
||||||
|
if len(txt) == 255 {
|
||||||
|
return fmt.Errorf("txtstring length is 255")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxtNoLongStrings audits TXT records for strings that are >255 octets.
|
||||||
|
func TxtNoLongStrings(records []*models.RecordConfig) error {
|
||||||
|
for _, rc := range records {
|
||||||
|
|
||||||
|
if rc.HasFormatIdenticalToTXT() { // TXT and similar:
|
||||||
|
for _, txt := range rc.TxtStrings {
|
||||||
|
if len(txt) > 255 {
|
||||||
|
return fmt.Errorf("txtstring length > 255")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxtNoMultipleStrings audits TXT records for multiple strings
|
||||||
|
func TxtNoMultipleStrings(records []*models.RecordConfig) error {
|
||||||
|
for _, rc := range records {
|
||||||
|
|
||||||
|
if rc.HasFormatIdenticalToTXT() { // TXT and similar:
|
||||||
|
if len(rc.TxtStrings) > 1 {
|
||||||
|
return fmt.Errorf("multiple strings in one txt")
|
||||||
|
} else if len(rc.TxtStrings) == 1 && len(rc.TxtStrings[0]) > 255 {
|
||||||
|
return fmt.Errorf("strings >255 octets")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxtNoTrailingSpace audits TXT records for strings that end with space.
|
||||||
|
func TxtNoTrailingSpace(records []*models.RecordConfig) error {
|
||||||
|
for _, rc := range records {
|
||||||
|
|
||||||
|
if rc.HasFormatIdenticalToTXT() { // TXT and similar:
|
||||||
|
for _, txt := range rc.TxtStrings {
|
||||||
|
if txt != "" && txt[ultimate(txt)] == ' ' {
|
||||||
|
return fmt.Errorf("txtstring ends with space")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxtNotEmpty audits TXT records for empty strings.
|
||||||
|
func TxtNotEmpty(records []*models.RecordConfig) error {
|
||||||
|
for _, rc := range records {
|
||||||
|
|
||||||
|
if rc.HasFormatIdenticalToTXT() { // TXT and similar:
|
||||||
|
// There must be strings.
|
||||||
|
if len(rc.TxtStrings) == 0 {
|
||||||
|
return fmt.Errorf("txt with no strings")
|
||||||
|
}
|
||||||
|
// Each string must be non-empty.
|
||||||
|
for _, txt := range rc.TxtStrings {
|
||||||
|
if len(txt) == 0 {
|
||||||
|
return fmt.Errorf("txtstring is empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
21
pkg/recordaudit/ultimate.go
Normal file
21
pkg/recordaudit/ultimate.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package recordaudit
|
||||||
|
|
||||||
|
/*
|
||||||
|
I proposed that Go add something like "len()" that returns the highest
|
||||||
|
index. This would avoid off-by-one errors. The proposed names include
|
||||||
|
ultimate(), ult(), high(), highest().
|
||||||
|
|
||||||
|
Nay-sayers said I should implement this as a function and see if I
|
||||||
|
actually used it. (I suspect the nay-sayers are perfect people that
|
||||||
|
never make off-by-one errors.)
|
||||||
|
|
||||||
|
That's what this file is about. It should be exactly the same (except
|
||||||
|
the first line) anywhere this is needed. After a few years I'll be
|
||||||
|
able to report if it actually helped.
|
||||||
|
|
||||||
|
Go will in-line this function.
|
||||||
|
*/
|
||||||
|
|
||||||
|
func ultimate(s string) int {
|
||||||
|
return len(s) - 1
|
||||||
|
}
|
33
pkg/txtutil/txtutil.go
Normal file
33
pkg/txtutil/txtutil.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package txtutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SplitSingleLongTxt finds TXT records with a single long string and splits it
|
||||||
|
// into 255-octet chunks. This is used by providers that, when a user specifies
|
||||||
|
// one long TXT string, split it into smaller strings behind the scenes.
|
||||||
|
// Typically this replaces the TXTMulti capability.
|
||||||
|
func SplitSingleLongTxt(records []*models.RecordConfig) {
|
||||||
|
for _, rc := range records {
|
||||||
|
if rc.HasFormatIdenticalToTXT() {
|
||||||
|
s := rc.TxtStrings[0]
|
||||||
|
if len(rc.TxtStrings) == 1 && len(s) > 255 {
|
||||||
|
rc.SetTargetTXTs(splitChunks(s, 255))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitChunks(buf string, lim int) []string {
|
||||||
|
var chunk string
|
||||||
|
chunks := make([]string, 0, len(buf)/lim+1)
|
||||||
|
for len(buf) >= lim {
|
||||||
|
chunk, buf = buf[:lim], buf[lim:]
|
||||||
|
chunks = append(chunks, chunk)
|
||||||
|
}
|
||||||
|
if len(buf) > 0 {
|
||||||
|
chunks = append(chunks, buf[:])
|
||||||
|
}
|
||||||
|
return chunks
|
||||||
|
}
|
35
pkg/txtutil/txtutil_test.go
Normal file
35
pkg/txtutil/txtutil_test.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package txtutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_splitChunks(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
buf string
|
||||||
|
lim int
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want []string
|
||||||
|
}{
|
||||||
|
{"0", args{"", 3}, []string{}},
|
||||||
|
{"1", args{"a", 3}, []string{"a"}},
|
||||||
|
{"2", args{"ab", 3}, []string{"ab"}},
|
||||||
|
{"3", args{"abc", 3}, []string{"abc"}},
|
||||||
|
{"4", args{"abcd", 3}, []string{"abc", "d"}},
|
||||||
|
{"5", args{"abcde", 3}, []string{"abc", "de"}},
|
||||||
|
{"6", args{"abcdef", 3}, []string{"abc", "def"}},
|
||||||
|
{"7", args{"abcdefg", 3}, []string{"abc", "def", "g"}},
|
||||||
|
{"8", args{"abcdefgh", 3}, []string{"abc", "def", "gh"}},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := splitChunks(tt.args.buf, tt.args.lim); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("splitChunks() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -30,7 +30,11 @@ var features = providers.DocumentationNotes{
|
|||||||
// Register with the dnscontrol system.
|
// Register with the dnscontrol system.
|
||||||
// This establishes the name (all caps), and the function to call to initialize it.
|
// This establishes the name (all caps), and the function to call to initialize it.
|
||||||
func init() {
|
func init() {
|
||||||
providers.RegisterDomainServiceProviderType("ACTIVEDIRECTORY_PS", newDNS, features)
|
fns := providers.DspFuncs{
|
||||||
|
Initializer: newDNS,
|
||||||
|
AuditRecordsor: AuditRecords,
|
||||||
|
}
|
||||||
|
providers.RegisterDomainServiceProviderType("ACTIVEDIRECTORY_PS", fns, features)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDNS(config map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
|
func newDNS(config map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
|
||||||
|
11
providers/activedir/auditrecords.go
Normal file
11
providers/activedir/auditrecords.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package activedir
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
11
providers/axfrddns/auditrecords.go
Normal file
11
providers/axfrddns/auditrecords.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package axfrddns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -25,6 +25,7 @@ import (
|
|||||||
|
|
||||||
"github.com/StackExchange/dnscontrol/v3/models"
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
|
||||||
"github.com/StackExchange/dnscontrol/v3/providers"
|
"github.com/StackExchange/dnscontrol/v3/providers"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -41,7 +42,6 @@ var features = providers.DocumentationNotes{
|
|||||||
providers.CanUseSRV: providers.Can(),
|
providers.CanUseSRV: providers.Can(),
|
||||||
providers.CanUseSSHFP: providers.Can(),
|
providers.CanUseSSHFP: providers.Can(),
|
||||||
providers.CanUseTLSA: providers.Can(),
|
providers.CanUseTLSA: providers.Can(),
|
||||||
providers.CanUseTXTMulti: providers.Can(),
|
|
||||||
providers.CanAutoDNSSEC: providers.Can("Just warn when DNSSEC is requested but no RRSIG is found in the AXFR or warn when DNSSEC is not requested but RRSIG are found in the AXFR."),
|
providers.CanAutoDNSSEC: providers.Can("Just warn when DNSSEC is requested but no RRSIG is found in the AXFR or warn when DNSSEC is not requested but RRSIG are found in the AXFR."),
|
||||||
providers.CantUseNOPURGE: providers.Cannot(),
|
providers.CantUseNOPURGE: providers.Cannot(),
|
||||||
providers.DocCreateDomains: providers.Cannot(),
|
providers.DocCreateDomains: providers.Cannot(),
|
||||||
@ -117,7 +117,11 @@ func initAxfrDdns(config map[string]string, providermeta json.RawMessage) (provi
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
providers.RegisterDomainServiceProviderType("AXFRDDNS", initAxfrDdns, features)
|
fns := providers.DspFuncs{
|
||||||
|
Initializer: initAxfrDdns,
|
||||||
|
AuditRecordsor: AuditRecords,
|
||||||
|
}
|
||||||
|
providers.RegisterDomainServiceProviderType("AXFRDDNS", fns, features)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Param is used to decode extra parameters sent to provider.
|
// Param is used to decode extra parameters sent to provider.
|
||||||
@ -289,6 +293,7 @@ func (c *axfrddnsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mod
|
|||||||
|
|
||||||
// Normalize
|
// Normalize
|
||||||
models.PostProcessRecords(foundRecords)
|
models.PostProcessRecords(foundRecords)
|
||||||
|
txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
|
||||||
|
|
||||||
differ := diff.New(dc)
|
differ := diff.New(dc)
|
||||||
_, create, del, mod, err := differ.IncrementalDiff(foundRecords)
|
_, create, del, mod, err := differ.IncrementalDiff(foundRecords)
|
||||||
|
11
providers/azuredns/auditrecords.go
Normal file
11
providers/azuredns/auditrecords.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package azuredns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -14,6 +14,7 @@ import (
|
|||||||
|
|
||||||
"github.com/StackExchange/dnscontrol/v3/models"
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
|
||||||
"github.com/StackExchange/dnscontrol/v3/providers"
|
"github.com/StackExchange/dnscontrol/v3/providers"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -58,7 +59,6 @@ var features = providers.DocumentationNotes{
|
|||||||
providers.DocOfficiallySupported: providers.Can(),
|
providers.DocOfficiallySupported: providers.Can(),
|
||||||
providers.CanUsePTR: providers.Can(),
|
providers.CanUsePTR: providers.Can(),
|
||||||
providers.CanUseSRV: providers.Can(),
|
providers.CanUseSRV: providers.Can(),
|
||||||
providers.CanUseTXTMulti: providers.Can(),
|
|
||||||
providers.CanUseCAA: providers.Can(),
|
providers.CanUseCAA: providers.Can(),
|
||||||
providers.CanUseNAPTR: providers.Cannot(),
|
providers.CanUseNAPTR: providers.Cannot(),
|
||||||
providers.CanUseSSHFP: providers.Cannot(),
|
providers.CanUseSSHFP: providers.Cannot(),
|
||||||
@ -68,7 +68,11 @@ var features = providers.DocumentationNotes{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
providers.RegisterDomainServiceProviderType("AZURE_DNS", newAzureDNSDsp, features)
|
fns := providers.DspFuncs{
|
||||||
|
Initializer: newAzureDNSDsp,
|
||||||
|
AuditRecordsor: AuditRecords,
|
||||||
|
}
|
||||||
|
providers.RegisterDomainServiceProviderType("AZURE_DNS", fns, features)
|
||||||
providers.RegisterCustomRecordType("AZURE_ALIAS", "AZURE_DNS", "")
|
providers.RegisterCustomRecordType("AZURE_ALIAS", "AZURE_DNS", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,7 +186,10 @@ func (a *azurednsProvider) getExistingRecords(domain string) (models.Records, []
|
|||||||
existingRecords = append(existingRecords, nativeToRecords(set, zoneName)...)
|
existingRecords = append(existingRecords, nativeToRecords(set, zoneName)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME(tlim): PostProcessRecords is usually called in GetDomainCorrections.
|
||||||
models.PostProcessRecords(existingRecords)
|
models.PostProcessRecords(existingRecords)
|
||||||
|
|
||||||
|
// FIXME(tlim): The "records" return value is usually stored in RecordConfig.Original.
|
||||||
return existingRecords, records, zoneName, nil
|
return existingRecords, records, zoneName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,6 +207,8 @@ func (a *azurednsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mod
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
|
||||||
|
|
||||||
differ := diff.New(dc)
|
differ := diff.New(dc)
|
||||||
namesToUpdate, err := differ.ChangedGroups(existingRecords)
|
namesToUpdate, err := differ.ChangedGroups(existingRecords)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
11
providers/bind/auditrecords.go
Normal file
11
providers/bind/auditrecords.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package bind
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -28,6 +28,7 @@ import (
|
|||||||
"github.com/StackExchange/dnscontrol/v3/models"
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
||||||
"github.com/StackExchange/dnscontrol/v3/pkg/prettyzone"
|
"github.com/StackExchange/dnscontrol/v3/pkg/prettyzone"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
|
||||||
"github.com/StackExchange/dnscontrol/v3/providers"
|
"github.com/StackExchange/dnscontrol/v3/providers"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -39,7 +40,6 @@ var features = providers.DocumentationNotes{
|
|||||||
providers.CanUseSRV: providers.Can(),
|
providers.CanUseSRV: providers.Can(),
|
||||||
providers.CanUseSSHFP: providers.Can(),
|
providers.CanUseSSHFP: providers.Can(),
|
||||||
providers.CanUseTLSA: providers.Can(),
|
providers.CanUseTLSA: providers.Can(),
|
||||||
providers.CanUseTXTMulti: providers.Can(),
|
|
||||||
providers.CanAutoDNSSEC: providers.Can("Just writes out a comment indicating DNSSEC was requested"),
|
providers.CanAutoDNSSEC: providers.Can("Just writes out a comment indicating DNSSEC was requested"),
|
||||||
providers.CantUseNOPURGE: providers.Cannot(),
|
providers.CantUseNOPURGE: providers.Cannot(),
|
||||||
providers.DocCreateDomains: providers.Can("Driver just maintains list of zone files. It should automatically add missing ones."),
|
providers.DocCreateDomains: providers.Can("Driver just maintains list of zone files. It should automatically add missing ones."),
|
||||||
@ -77,7 +77,11 @@ func initBind(config map[string]string, providermeta json.RawMessage) (providers
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
providers.RegisterDomainServiceProviderType("BIND", initBind, features)
|
fns := providers.DspFuncs{
|
||||||
|
Initializer: initBind,
|
||||||
|
AuditRecordsor: AuditRecords,
|
||||||
|
}
|
||||||
|
providers.RegisterDomainServiceProviderType("BIND", fns, features)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SoaInfo contains the parts of the default SOA settings.
|
// SoaInfo contains the parts of the default SOA settings.
|
||||||
@ -228,6 +232,7 @@ func (c *bindProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.
|
|||||||
|
|
||||||
// Normalize
|
// Normalize
|
||||||
models.PostProcessRecords(foundRecords)
|
models.PostProcessRecords(foundRecords)
|
||||||
|
txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
|
||||||
|
|
||||||
differ := diff.New(dc)
|
differ := diff.New(dc)
|
||||||
_, create, del, mod, err := differ.IncrementalDiff(foundRecords)
|
_, create, del, mod, err := differ.IncrementalDiff(foundRecords)
|
||||||
|
@ -41,9 +41,6 @@ const (
|
|||||||
// CanUseTLSA indicates the provider can handle TLSA records
|
// CanUseTLSA indicates the provider can handle TLSA records
|
||||||
CanUseTLSA
|
CanUseTLSA
|
||||||
|
|
||||||
// CanUseTXTMulti indicates the provider can handle TXT records with multiple strings
|
|
||||||
CanUseTXTMulti
|
|
||||||
|
|
||||||
// CanAutoDNSSEC indicates that the provider can automatically handle DNSSEC,
|
// CanAutoDNSSEC indicates that the provider can automatically handle DNSSEC,
|
||||||
// so folks can ask for that.
|
// so folks can ask for that.
|
||||||
CanAutoDNSSEC
|
CanAutoDNSSEC
|
||||||
|
@ -17,20 +17,19 @@ func _() {
|
|||||||
_ = x[CanUseSRV-6]
|
_ = x[CanUseSRV-6]
|
||||||
_ = x[CanUseSSHFP-7]
|
_ = x[CanUseSSHFP-7]
|
||||||
_ = x[CanUseTLSA-8]
|
_ = x[CanUseTLSA-8]
|
||||||
_ = x[CanUseTXTMulti-9]
|
_ = x[CanAutoDNSSEC-9]
|
||||||
_ = x[CanAutoDNSSEC-10]
|
_ = x[CantUseNOPURGE-10]
|
||||||
_ = x[CantUseNOPURGE-11]
|
_ = x[DocOfficiallySupported-11]
|
||||||
_ = x[DocOfficiallySupported-12]
|
_ = x[DocDualHost-12]
|
||||||
_ = x[DocDualHost-13]
|
_ = x[DocCreateDomains-13]
|
||||||
_ = x[DocCreateDomains-14]
|
_ = x[CanUseRoute53Alias-14]
|
||||||
_ = x[CanUseRoute53Alias-15]
|
_ = x[CanGetZones-15]
|
||||||
_ = x[CanGetZones-16]
|
_ = x[CanUseAzureAlias-16]
|
||||||
_ = x[CanUseAzureAlias-17]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const _Capability_name = "CanUseAliasCanUseCAACanUseDSCanUseDSForChildrenCanUsePTRCanUseNAPTRCanUseSRVCanUseSSHFPCanUseTLSACanUseTXTMultiCanAutoDNSSECCantUseNOPURGEDocOfficiallySupportedDocDualHostDocCreateDomainsCanUseRoute53AliasCanGetZonesCanUseAzureAlias"
|
const _Capability_name = "CanUseAliasCanUseCAACanUseDSCanUseDSForChildrenCanUsePTRCanUseNAPTRCanUseSRVCanUseSSHFPCanUseTLSACanAutoDNSSECCantUseNOPURGEDocOfficiallySupportedDocDualHostDocCreateDomainsCanUseRoute53AliasCanGetZonesCanUseAzureAlias"
|
||||||
|
|
||||||
var _Capability_index = [...]uint8{0, 11, 20, 28, 47, 56, 67, 76, 87, 97, 111, 124, 138, 160, 171, 187, 205, 216, 232}
|
var _Capability_index = [...]uint8{0, 11, 20, 28, 47, 56, 67, 76, 87, 97, 110, 124, 146, 157, 173, 191, 202, 218}
|
||||||
|
|
||||||
func (i Capability) String() string {
|
func (i Capability) String() string {
|
||||||
if i >= Capability(len(_Capability_index)-1) {
|
if i >= Capability(len(_Capability_index)-1) {
|
||||||
|
11
providers/cloudflare/auditrecords.go
Normal file
11
providers/cloudflare/auditrecords.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
||||||
"github.com/StackExchange/dnscontrol/v3/pkg/printer"
|
"github.com/StackExchange/dnscontrol/v3/pkg/printer"
|
||||||
"github.com/StackExchange/dnscontrol/v3/pkg/transform"
|
"github.com/StackExchange/dnscontrol/v3/pkg/transform"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
|
||||||
"github.com/StackExchange/dnscontrol/v3/providers"
|
"github.com/StackExchange/dnscontrol/v3/providers"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -45,7 +46,6 @@ var features = providers.DocumentationNotes{
|
|||||||
providers.CanUseTLSA: providers.Can(),
|
providers.CanUseTLSA: providers.Can(),
|
||||||
providers.CanUseSSHFP: providers.Can(),
|
providers.CanUseSSHFP: providers.Can(),
|
||||||
providers.CanUseDSForChildren: providers.Can(),
|
providers.CanUseDSForChildren: providers.Can(),
|
||||||
providers.CanUseTXTMulti: providers.Can(),
|
|
||||||
providers.DocCreateDomains: providers.Can(),
|
providers.DocCreateDomains: providers.Can(),
|
||||||
providers.DocDualHost: providers.Cannot("Cloudflare will not work well in situations where it is not the only DNS server"),
|
providers.DocDualHost: providers.Cannot("Cloudflare will not work well in situations where it is not the only DNS server"),
|
||||||
providers.DocOfficiallySupported: providers.Can(),
|
providers.DocOfficiallySupported: providers.Can(),
|
||||||
@ -53,7 +53,11 @@ var features = providers.DocumentationNotes{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
providers.RegisterDomainServiceProviderType("CLOUDFLAREAPI", newCloudflare, features)
|
fns := providers.DspFuncs{
|
||||||
|
Initializer: newCloudflare,
|
||||||
|
AuditRecordsor: AuditRecords,
|
||||||
|
}
|
||||||
|
providers.RegisterDomainServiceProviderType("CLOUDFLAREAPI", fns, features)
|
||||||
providers.RegisterCustomRecordType("CF_REDIRECT", "CLOUDFLAREAPI", "")
|
providers.RegisterCustomRecordType("CF_REDIRECT", "CLOUDFLAREAPI", "")
|
||||||
providers.RegisterCustomRecordType("CF_TEMP_REDIRECT", "CLOUDFLAREAPI", "")
|
providers.RegisterCustomRecordType("CF_TEMP_REDIRECT", "CLOUDFLAREAPI", "")
|
||||||
}
|
}
|
||||||
@ -206,6 +210,7 @@ func (c *cloudflareProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*m
|
|||||||
|
|
||||||
// Normalize
|
// Normalize
|
||||||
models.PostProcessRecords(records)
|
models.PostProcessRecords(records)
|
||||||
|
txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
|
||||||
|
|
||||||
differ := diff.New(dc, getProxyMetadata)
|
differ := diff.New(dc, getProxyMetadata)
|
||||||
_, create, del, mod, err := differ.IncrementalDiff(records)
|
_, create, del, mod, err := differ.IncrementalDiff(records)
|
||||||
|
@ -195,9 +195,7 @@ func (c *cloudflareProvider) createRec(rec *models.RecordConfig, domainID string
|
|||||||
prio = fmt.Sprintf(" %d ", rec.MxPreference)
|
prio = fmt.Sprintf(" %d ", rec.MxPreference)
|
||||||
}
|
}
|
||||||
if rec.Type == "TXT" {
|
if rec.Type == "TXT" {
|
||||||
if len(rec.TxtStrings) > 1 {
|
content = rec.GetTargetField()
|
||||||
content = `"` + strings.Join(rec.TxtStrings, `" "`) + `"`
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if rec.Type == "DS" {
|
if rec.Type == "DS" {
|
||||||
content = fmt.Sprintf("%d %d %d %s", rec.DsKeyTag, rec.DsAlgorithm, rec.DsDigestType, rec.DsDigest)
|
content = fmt.Sprintf("%d %d %d %s", rec.DsKeyTag, rec.DsAlgorithm, rec.DsDigestType, rec.DsDigest)
|
||||||
|
28
providers/cloudns/auditrecords.go
Normal file
28
providers/cloudns/auditrecords.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package cloudns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/pkg/recordaudit"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
|
||||||
|
if err := recordaudit.TxtNoBackticks(records); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Still needed as of 2021-03-01
|
||||||
|
|
||||||
|
if err := recordaudit.TxtNotEmpty(records); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Still needed as of 2021-03-01
|
||||||
|
|
||||||
|
if err := recordaudit.TxtNoTrailingSpace(records); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Still needed as of 2021-03-01
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/StackExchange/dnscontrol/v3/models"
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
|
||||||
"github.com/StackExchange/dnscontrol/v3/providers"
|
"github.com/StackExchange/dnscontrol/v3/providers"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -49,12 +50,15 @@ var features = providers.DocumentationNotes{
|
|||||||
providers.CanUsePTR: providers.Can(),
|
providers.CanUsePTR: providers.Can(),
|
||||||
providers.CanGetZones: providers.Can(),
|
providers.CanGetZones: providers.Can(),
|
||||||
providers.CanUseDSForChildren: providers.Can(),
|
providers.CanUseDSForChildren: providers.Can(),
|
||||||
providers.CanUseTXTMulti: providers.Can(),
|
|
||||||
//providers.CanUseDS: providers.Can(), // in ClouDNS we can add DS record just for a subdomain(child)
|
//providers.CanUseDS: providers.Can(), // in ClouDNS we can add DS record just for a subdomain(child)
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
providers.RegisterDomainServiceProviderType("CLOUDNS", NewCloudns, features)
|
fns := providers.DspFuncs{
|
||||||
|
Initializer: NewCloudns,
|
||||||
|
AuditRecordsor: AuditRecords,
|
||||||
|
}
|
||||||
|
providers.RegisterDomainServiceProviderType("CLOUDNS", fns, features)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNameservers returns the nameservers for a domain.
|
// GetNameservers returns the nameservers for a domain.
|
||||||
@ -90,6 +94,7 @@ func (c *cloudnsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode
|
|||||||
}
|
}
|
||||||
// Normalize
|
// Normalize
|
||||||
models.PostProcessRecords(existingRecords)
|
models.PostProcessRecords(existingRecords)
|
||||||
|
txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
|
||||||
|
|
||||||
// ClouDNS doesn't allow selecting an arbitrary TTL, only a set of predefined values https://asia.cloudns.net/wiki/article/188/
|
// ClouDNS doesn't allow selecting an arbitrary TTL, only a set of predefined values https://asia.cloudns.net/wiki/article/188/
|
||||||
// We need to make sure we don't change it every time if it is as close as it's going to get
|
// We need to make sure we don't change it every time if it is as close as it's going to get
|
||||||
|
11
providers/cscglobal/auditrecords.go
Normal file
11
providers/cscglobal/auditrecords.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package cscglobal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
11
providers/desec/auditrecords.go
Normal file
11
providers/desec/auditrecords.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package desec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -27,7 +27,6 @@ func nativeToRecords(n resourceRecord, origin string) (rcs []*models.RecordConfi
|
|||||||
switch rtype := n.Type; rtype {
|
switch rtype := n.Type; rtype {
|
||||||
case "TXT":
|
case "TXT":
|
||||||
rc.SetTargetTXT(value)
|
rc.SetTargetTXT(value)
|
||||||
rc.TxtNormalize("multistring")
|
|
||||||
default: // "A", "AAAA", "CAA", "NS", "CNAME", "MX", "PTR", "SRV"
|
default: // "A", "AAAA", "CAA", "NS", "CNAME", "MX", "PTR", "SRV"
|
||||||
if err := rc.PopulateFromString(rtype, value, origin); err != nil {
|
if err := rc.PopulateFromString(rtype, value, origin); err != nil {
|
||||||
panic(fmt.Errorf("unparsable record received from deSEC: %w", err))
|
panic(fmt.Errorf("unparsable record received from deSEC: %w", err))
|
||||||
|
@ -48,7 +48,6 @@ var features = providers.DocumentationNotes{
|
|||||||
providers.CanUsePTR: providers.Can(),
|
providers.CanUsePTR: providers.Can(),
|
||||||
providers.CanGetZones: providers.Can(),
|
providers.CanGetZones: providers.Can(),
|
||||||
providers.CanAutoDNSSEC: providers.Cannot(),
|
providers.CanAutoDNSSEC: providers.Cannot(),
|
||||||
providers.CanUseTXTMulti: providers.Can(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultNameServerNames = []string{
|
var defaultNameServerNames = []string{
|
||||||
@ -57,7 +56,11 @@ var defaultNameServerNames = []string{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
providers.RegisterDomainServiceProviderType("DESEC", NewDeSec, features)
|
fns := providers.DspFuncs{
|
||||||
|
Initializer: NewDeSec,
|
||||||
|
AuditRecordsor: AuditRecords,
|
||||||
|
}
|
||||||
|
providers.RegisterDomainServiceProviderType("DESEC", fns, features)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNameservers returns the nameservers for a domain.
|
// GetNameservers returns the nameservers for a domain.
|
||||||
|
61
providers/digitalocean/auditrecords.go
Normal file
61
providers/digitalocean/auditrecords.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package digitalocean
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/pkg/recordaudit"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
|
||||||
|
// TODO(tlim): Audit CAA records.
|
||||||
|
// "Semicolons not supported in issue/issuewild fields.", "https://www.digitalocean.com/docs/networking/dns/how-to/create-caa-records"),
|
||||||
|
// Users are warned about these limits in docs/_providers/digitalocean.md
|
||||||
|
|
||||||
|
if err := MaxLengthDO(records); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Still needed as of 2021-03-01
|
||||||
|
|
||||||
|
// Double-quotes not permitted in TXT strings. I have a hunch that
|
||||||
|
// this is due to a broken parser on the DO side.
|
||||||
|
if err := recordaudit.TxtNoDoubleQuotes(records); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Still needed as of 2021-03-01
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxLengthDO returns and error if the strings are longer than
|
||||||
|
// permitted by DigitalOcean. Sadly their length limit is
|
||||||
|
// undocumented. This is a guess.
|
||||||
|
func MaxLengthDO(records []*models.RecordConfig) error {
|
||||||
|
// The total length of all strings can't be longer than 512; and in
|
||||||
|
// reality must be shorter due to sloppy validation checks.
|
||||||
|
// https://github.com/StackExchange/dnscontrol/issues/370
|
||||||
|
|
||||||
|
// DigitalOcean's TXT record implementation checks size limits
|
||||||
|
// wrong. RFC 1035 Section 3.3.14 states that each substring can be
|
||||||
|
// 255 octets, and there is no limit on the number of such
|
||||||
|
// substrings, aside from the usual packet length limits. DO's
|
||||||
|
// implementation restricts the total length to be 512 octets,
|
||||||
|
// including the quotes, backlashes used for escapes, spaces between
|
||||||
|
// substrings.
|
||||||
|
// In other words, they're doing the checking on the API protocol
|
||||||
|
// encoded data instead of on on the resulting TXT record. Sigh.
|
||||||
|
|
||||||
|
for _, rc := range records {
|
||||||
|
|
||||||
|
if rc.HasFormatIdenticalToTXT() { // TXT and similar:
|
||||||
|
if len(rc.GetTargetField()) > 509 {
|
||||||
|
return fmt.Errorf("encoded txt too long")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/StackExchange/dnscontrol/v3/models"
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
|
||||||
"github.com/StackExchange/dnscontrol/v3/providers"
|
"github.com/StackExchange/dnscontrol/v3/providers"
|
||||||
"github.com/miekg/dns/dnsutil"
|
"github.com/miekg/dns/dnsutil"
|
||||||
|
|
||||||
@ -76,11 +77,14 @@ var features = providers.DocumentationNotes{
|
|||||||
providers.CanUseSRV: providers.Can(),
|
providers.CanUseSRV: providers.Can(),
|
||||||
providers.CanUseCAA: providers.Can("Semicolons not supported in issue/issuewild fields.", "https://www.digitalocean.com/docs/networking/dns/how-to/create-caa-records"),
|
providers.CanUseCAA: providers.Can("Semicolons not supported in issue/issuewild fields.", "https://www.digitalocean.com/docs/networking/dns/how-to/create-caa-records"),
|
||||||
providers.CanGetZones: providers.Can(),
|
providers.CanGetZones: providers.Can(),
|
||||||
providers.CanUseTXTMulti: providers.Can("A broken parser prevents TXTMulti strings from including double-quotes; The total length of all strings can't be longer than 512; and in reality must be shorter due to sloppy validation checks.", "https://github.com/StackExchange/dnscontrol/issues/370"),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
providers.RegisterDomainServiceProviderType("DIGITALOCEAN", NewDo, features)
|
fns := providers.DspFuncs{
|
||||||
|
Initializer: NewDo,
|
||||||
|
AuditRecordsor: AuditRecords,
|
||||||
|
}
|
||||||
|
providers.RegisterDomainServiceProviderType("DIGITALOCEAN", fns, features)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnsureDomainExists returns an error if domain doesn't exist.
|
// EnsureDomainExists returns an error if domain doesn't exist.
|
||||||
@ -140,6 +144,7 @@ func (api *digitaloceanProvider) GetDomainCorrections(dc *models.DomainConfig) (
|
|||||||
|
|
||||||
// Normalize
|
// Normalize
|
||||||
models.PostProcessRecords(existingRecords)
|
models.PostProcessRecords(existingRecords)
|
||||||
|
txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
|
||||||
|
|
||||||
differ := diff.New(dc)
|
differ := diff.New(dc)
|
||||||
_, create, delete, modify, err := differ.IncrementalDiff(existingRecords)
|
_, create, delete, modify, err := differ.IncrementalDiff(existingRecords)
|
||||||
@ -290,11 +295,11 @@ func toReq(dc *models.DomainConfig, rc *models.RecordConfig) *godo.DomainRecordE
|
|||||||
priority = int(rc.SrvPriority)
|
priority = int(rc.SrvPriority)
|
||||||
case "TXT":
|
case "TXT":
|
||||||
// TXT records are the one place where DO combines many items into one field.
|
// TXT records are the one place where DO combines many items into one field.
|
||||||
target = rc.GetTargetCombined()
|
target = rc.GetTargetField()
|
||||||
case "CAA":
|
case "CAA":
|
||||||
// DO API requires that value ends in dot
|
// DO API requires that a CAA target ends in dot.
|
||||||
// But the value returned from API doesn't contain this,
|
// Interestingly enough, the value returned from API doesn't
|
||||||
// so no need to strip the dot when reading value from API.
|
// contain a trailing dot.
|
||||||
target = target + "."
|
target = target + "."
|
||||||
default:
|
default:
|
||||||
// no action required
|
// no action required
|
||||||
|
11
providers/dnsimple/auditrecords.go
Normal file
11
providers/dnsimple/auditrecords.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package dnsimple
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -13,6 +13,7 @@ import (
|
|||||||
|
|
||||||
"github.com/StackExchange/dnscontrol/v3/models"
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
|
||||||
"github.com/StackExchange/dnscontrol/v3/providers"
|
"github.com/StackExchange/dnscontrol/v3/providers"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -24,7 +25,6 @@ var features = providers.DocumentationNotes{
|
|||||||
providers.CanUsePTR: providers.Can(),
|
providers.CanUsePTR: providers.Can(),
|
||||||
providers.CanUseSSHFP: providers.Can(),
|
providers.CanUseSSHFP: providers.Can(),
|
||||||
providers.CanUseSRV: providers.Can(),
|
providers.CanUseSRV: providers.Can(),
|
||||||
providers.CanUseTXTMulti: providers.Can(),
|
|
||||||
providers.CanAutoDNSSEC: providers.Can(),
|
providers.CanAutoDNSSEC: providers.Can(),
|
||||||
providers.CanUseTLSA: providers.Cannot(),
|
providers.CanUseTLSA: providers.Cannot(),
|
||||||
providers.DocCreateDomains: providers.Cannot(),
|
providers.DocCreateDomains: providers.Cannot(),
|
||||||
@ -35,7 +35,11 @@ var features = providers.DocumentationNotes{
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
providers.RegisterRegistrarType("DNSIMPLE", newReg)
|
providers.RegisterRegistrarType("DNSIMPLE", newReg)
|
||||||
providers.RegisterDomainServiceProviderType("DNSIMPLE", newDsp, features)
|
fns := providers.DspFuncs{
|
||||||
|
Initializer: newDsp,
|
||||||
|
AuditRecordsor: AuditRecords,
|
||||||
|
}
|
||||||
|
providers.RegisterDomainServiceProviderType("DNSIMPLE", fns, features)
|
||||||
}
|
}
|
||||||
|
|
||||||
const stateRegistered = "registered"
|
const stateRegistered = "registered"
|
||||||
@ -145,6 +149,7 @@ func (c *dnsimpleProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mod
|
|||||||
|
|
||||||
// Normalize
|
// Normalize
|
||||||
models.PostProcessRecords(actual)
|
models.PostProcessRecords(actual)
|
||||||
|
txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
|
||||||
|
|
||||||
differ := diff.New(dc)
|
differ := diff.New(dc)
|
||||||
_, create, del, modify, err := differ.IncrementalDiff(actual)
|
_, create, del, modify, err := differ.IncrementalDiff(actual)
|
||||||
|
11
providers/doh/auditrecords.go
Normal file
11
providers/doh/auditrecords.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package doh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
11
providers/exoscale/auditrecords.go
Normal file
11
providers/exoscale/auditrecords.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package exoscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -37,7 +37,11 @@ var features = providers.DocumentationNotes{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
providers.RegisterDomainServiceProviderType("EXOSCALE", NewExoscale, features)
|
fns := providers.DspFuncs{
|
||||||
|
Initializer: NewExoscale,
|
||||||
|
AuditRecordsor: AuditRecords,
|
||||||
|
}
|
||||||
|
providers.RegisterDomainServiceProviderType("EXOSCALE", fns, features)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnsureDomainExists returns an error if domain doesn't exist.
|
// EnsureDomainExists returns an error if domain doesn't exist.
|
||||||
|
11
providers/gandi_v5/auditrecords.go
Normal file
11
providers/gandi_v5/auditrecords.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package gandi5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -27,6 +27,7 @@ import (
|
|||||||
"github.com/StackExchange/dnscontrol/v3/models"
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
||||||
"github.com/StackExchange/dnscontrol/v3/pkg/printer"
|
"github.com/StackExchange/dnscontrol/v3/pkg/printer"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
|
||||||
"github.com/StackExchange/dnscontrol/v3/providers"
|
"github.com/StackExchange/dnscontrol/v3/providers"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -34,7 +35,11 @@ import (
|
|||||||
|
|
||||||
// init registers the provider to dnscontrol.
|
// init registers the provider to dnscontrol.
|
||||||
func init() {
|
func init() {
|
||||||
providers.RegisterDomainServiceProviderType("GANDI_V5", newDsp, features)
|
fns := providers.DspFuncs{
|
||||||
|
Initializer: newDsp,
|
||||||
|
AuditRecordsor: AuditRecords,
|
||||||
|
}
|
||||||
|
providers.RegisterDomainServiceProviderType("GANDI_V5", fns, features)
|
||||||
providers.RegisterRegistrarType("GANDI_V5", newReg)
|
providers.RegisterRegistrarType("GANDI_V5", newReg)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +51,6 @@ var features = providers.DocumentationNotes{
|
|||||||
providers.CanUseSRV: providers.Can(),
|
providers.CanUseSRV: providers.Can(),
|
||||||
providers.CanUseSSHFP: providers.Can(),
|
providers.CanUseSSHFP: providers.Can(),
|
||||||
providers.CanUseTLSA: providers.Can(),
|
providers.CanUseTLSA: providers.Can(),
|
||||||
providers.CanUseTXTMulti: providers.Can(),
|
|
||||||
providers.CantUseNOPURGE: providers.Cannot(),
|
providers.CantUseNOPURGE: providers.Cannot(),
|
||||||
providers.DocCreateDomains: providers.Cannot("Can only manage domains registered through their service"),
|
providers.DocCreateDomains: providers.Cannot("Can only manage domains registered through their service"),
|
||||||
providers.DocOfficiallySupported: providers.Cannot(),
|
providers.DocOfficiallySupported: providers.Cannot(),
|
||||||
@ -194,6 +198,8 @@ func (client *gandiv5Provider) GenerateDomainCorrections(dc *models.DomainConfig
|
|||||||
|
|
||||||
var corrections = []*models.Correction{}
|
var corrections = []*models.Correction{}
|
||||||
|
|
||||||
|
txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
|
||||||
|
|
||||||
// diff existing vs. current.
|
// diff existing vs. current.
|
||||||
differ := diff.New(dc)
|
differ := diff.New(dc)
|
||||||
keysToUpdate, err := differ.ChangedGroups(existing)
|
keysToUpdate, err := differ.ChangedGroups(existing)
|
||||||
|
11
providers/gcloud/auditrecords.go
Normal file
11
providers/gcloud/auditrecords.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -14,6 +14,7 @@ import (
|
|||||||
|
|
||||||
"github.com/StackExchange/dnscontrol/v3/models"
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
|
||||||
"github.com/StackExchange/dnscontrol/v3/providers"
|
"github.com/StackExchange/dnscontrol/v3/providers"
|
||||||
gdns "google.golang.org/api/dns/v1"
|
gdns "google.golang.org/api/dns/v1"
|
||||||
)
|
)
|
||||||
@ -26,7 +27,6 @@ var features = providers.DocumentationNotes{
|
|||||||
providers.CanUseSRV: providers.Can(),
|
providers.CanUseSRV: providers.Can(),
|
||||||
providers.CanUseSSHFP: providers.Can(),
|
providers.CanUseSSHFP: providers.Can(),
|
||||||
providers.CanUseTLSA: providers.Can(),
|
providers.CanUseTLSA: providers.Can(),
|
||||||
providers.CanUseTXTMulti: providers.Can(),
|
|
||||||
providers.DocCreateDomains: providers.Can(),
|
providers.DocCreateDomains: providers.Can(),
|
||||||
providers.DocDualHost: providers.Can(),
|
providers.DocDualHost: providers.Can(),
|
||||||
providers.DocOfficiallySupported: providers.Can(),
|
providers.DocOfficiallySupported: providers.Can(),
|
||||||
@ -37,7 +37,11 @@ func sPtr(s string) *string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
providers.RegisterDomainServiceProviderType("GCLOUD", New, features)
|
fns := providers.DspFuncs{
|
||||||
|
Initializer: New,
|
||||||
|
AuditRecordsor: AuditRecords,
|
||||||
|
}
|
||||||
|
providers.RegisterDomainServiceProviderType("GCLOUD", fns, features)
|
||||||
}
|
}
|
||||||
|
|
||||||
type gcloudProvider struct {
|
type gcloudProvider struct {
|
||||||
@ -187,6 +191,7 @@ func (g *gcloudProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*model
|
|||||||
|
|
||||||
// Normalize
|
// Normalize
|
||||||
models.PostProcessRecords(existingRecords)
|
models.PostProcessRecords(existingRecords)
|
||||||
|
txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
|
||||||
|
|
||||||
// first collect keys that have changed
|
// first collect keys that have changed
|
||||||
differ := diff.New(dc)
|
differ := diff.New(dc)
|
||||||
|
11
providers/hedns/auditrecords.go
Normal file
11
providers/hedns/auditrecords.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package hedns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/PuerkitoBio/goquery"
|
"github.com/PuerkitoBio/goquery"
|
||||||
"github.com/StackExchange/dnscontrol/v3/models"
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
|
||||||
"github.com/StackExchange/dnscontrol/v3/providers"
|
"github.com/StackExchange/dnscontrol/v3/providers"
|
||||||
"github.com/pquerna/otp/totp"
|
"github.com/pquerna/otp/totp"
|
||||||
)
|
)
|
||||||
@ -51,7 +52,6 @@ var features = providers.DocumentationNotes{
|
|||||||
providers.CanUseSSHFP: providers.Can(),
|
providers.CanUseSSHFP: providers.Can(),
|
||||||
providers.CanUseSRV: providers.Can(),
|
providers.CanUseSRV: providers.Can(),
|
||||||
providers.CanUseTLSA: providers.Cannot(),
|
providers.CanUseTLSA: providers.Cannot(),
|
||||||
providers.CanUseTXTMulti: providers.Can(),
|
|
||||||
providers.CanAutoDNSSEC: providers.Cannot(),
|
providers.CanAutoDNSSEC: providers.Cannot(),
|
||||||
providers.DocCreateDomains: providers.Can(),
|
providers.DocCreateDomains: providers.Can(),
|
||||||
providers.DocDualHost: providers.Can(),
|
providers.DocDualHost: providers.Can(),
|
||||||
@ -60,7 +60,11 @@ var features = providers.DocumentationNotes{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
providers.RegisterDomainServiceProviderType("HEDNS", newHEDNSProvider, features)
|
fns := providers.DspFuncs{
|
||||||
|
Initializer: newHEDNSProvider,
|
||||||
|
AuditRecordsor: AuditRecords,
|
||||||
|
}
|
||||||
|
providers.RegisterDomainServiceProviderType("HEDNS", fns, features)
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultNameservers = []string{
|
var defaultNameservers = []string{
|
||||||
@ -199,6 +203,7 @@ func (c *hednsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models
|
|||||||
|
|
||||||
// Normalize
|
// Normalize
|
||||||
models.PostProcessRecords(prunedRecords)
|
models.PostProcessRecords(prunedRecords)
|
||||||
|
txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
|
||||||
|
|
||||||
differ := diff.New(dc)
|
differ := diff.New(dc)
|
||||||
_, toCreate, toDelete, toModify, err := differ.IncrementalDiff(prunedRecords)
|
_, toCreate, toDelete, toModify, err := differ.IncrementalDiff(prunedRecords)
|
||||||
|
11
providers/hetzner/auditrecords.go
Normal file
11
providers/hetzner/auditrecords.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package hetzner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/StackExchange/dnscontrol/v3/models"
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
|
||||||
"github.com/StackExchange/dnscontrol/v3/providers"
|
"github.com/StackExchange/dnscontrol/v3/providers"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,11 +23,14 @@ var features = providers.DocumentationNotes{
|
|||||||
providers.CanUseSRV: providers.Can(),
|
providers.CanUseSRV: providers.Can(),
|
||||||
providers.CanUseSSHFP: providers.Cannot(),
|
providers.CanUseSSHFP: providers.Cannot(),
|
||||||
providers.CanUseTLSA: providers.Cannot(),
|
providers.CanUseTLSA: providers.Cannot(),
|
||||||
providers.CanUseTXTMulti: providers.Can(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
providers.RegisterDomainServiceProviderType("HETZNER", New, features)
|
fns := providers.DspFuncs{
|
||||||
|
Initializer: New,
|
||||||
|
AuditRecordsor: AuditRecords,
|
||||||
|
}
|
||||||
|
providers.RegisterDomainServiceProviderType("HETZNER", fns, features)
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new API handle.
|
// New creates a new API handle.
|
||||||
@ -93,6 +97,7 @@ func (api *hetznerProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mo
|
|||||||
|
|
||||||
// Normalize
|
// Normalize
|
||||||
models.PostProcessRecords(existingRecords)
|
models.PostProcessRecords(existingRecords)
|
||||||
|
txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
|
||||||
|
|
||||||
differ := diff.New(dc)
|
differ := diff.New(dc)
|
||||||
_, create, del, modify, err := differ.IncrementalDiff(existingRecords)
|
_, create, del, modify, err := differ.IncrementalDiff(existingRecords)
|
||||||
|
@ -2,6 +2,7 @@ package hetzner
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/StackExchange/dnscontrol/v3/models"
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type bulkCreateRecordsRequest struct {
|
type bulkCreateRecordsRequest struct {
|
||||||
@ -63,20 +64,22 @@ func fromRecordConfig(in *models.RecordConfig, zone *zone) *record {
|
|||||||
record := &record{
|
record := &record{
|
||||||
Name: in.GetLabel(),
|
Name: in.GetLabel(),
|
||||||
Type: in.Type,
|
Type: in.Type,
|
||||||
Value: in.GetTargetField(),
|
Value: in.GetTargetCombined(),
|
||||||
TTL: &ttl,
|
TTL: &ttl,
|
||||||
ZoneID: zone.ID,
|
ZoneID: zone.ID,
|
||||||
}
|
}
|
||||||
|
|
||||||
switch record.Type {
|
if record.Type == "TXT" && len(in.TxtStrings) == 1 {
|
||||||
case "TXT":
|
// HACK: HETZNER rejects values that fit into 255 bytes w/o quotes,
|
||||||
// Cannot use `in.GetTargetCombined()` for TXTs:
|
// but do not fit w/ added quotes (via GetTargetCombined()).
|
||||||
// Their validation would complain about a missing `;`.
|
// Sending the raw, non-quoted value works for the comprehensive
|
||||||
// Test case: single_TXT:Create_a_255-byte_TXT
|
// suite of integrations tests.
|
||||||
|
// The HETZNER validation does not provide helpful error messages.
|
||||||
// {"error":{"message":"422 Unprocessable Entity: missing: ; ","code":422}}
|
// {"error":{"message":"422 Unprocessable Entity: missing: ; ","code":422}}
|
||||||
record.Value = in.GetTargetField()
|
valueNotQuoted := in.TxtStrings[0]
|
||||||
default:
|
if len(valueNotQuoted) == 254 || len(valueNotQuoted) == 255 {
|
||||||
record.Value = in.GetTargetCombined()
|
record.Value = valueNotQuoted
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return record
|
return record
|
||||||
@ -90,7 +93,15 @@ func toRecordConfig(domain string, record *record) *models.RecordConfig {
|
|||||||
}
|
}
|
||||||
rc.SetLabel(record.Name, domain)
|
rc.SetLabel(record.Name, domain)
|
||||||
|
|
||||||
_ = rc.PopulateFromString(record.Type, record.Value, domain)
|
value := record.Value
|
||||||
|
// HACK: Hetzner is inserting a trailing space after multiple, quoted values.
|
||||||
|
// NOTE: The actual DNS answer does not contain the space.
|
||||||
|
if record.Type == "TXT" && len(value) > 0 && value[len(value)-1] == ' ' {
|
||||||
|
// Per RFC 1035 spaces outside quoted values are irrelevant.
|
||||||
|
value = strings.TrimRight(value, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = rc.PopulateFromString(record.Type, value, domain)
|
||||||
|
|
||||||
return rc
|
return rc
|
||||||
}
|
}
|
||||||
|
33
providers/hexonet/auditrecords.go
Normal file
33
providers/hexonet/auditrecords.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package hexonet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/pkg/recordaudit"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
|
||||||
|
if err := recordaudit.TxtNoLongStrings(records); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Still needed as of 2021-03-07
|
||||||
|
|
||||||
|
if err := recordaudit.TxtNoMultipleStrings(records); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Still needed as of 2021-03-07
|
||||||
|
|
||||||
|
if err := recordaudit.TxtNotEmpty(records); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Still needed as of 2021-03-01
|
||||||
|
|
||||||
|
if err := recordaudit.TxtNoTrailingSpace(records); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Still needed as of 2021-03-01
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -25,7 +25,6 @@ var features = providers.DocumentationNotes{
|
|||||||
providers.CanUseRoute53Alias: providers.Cannot("Using ALIAS is possible through our extended DNS (X-DNS) service. Feel free to get in touch with us."),
|
providers.CanUseRoute53Alias: providers.Cannot("Using ALIAS is possible through our extended DNS (X-DNS) service. Feel free to get in touch with us."),
|
||||||
providers.CanUseSRV: providers.Can("SRV records with empty targets are not supported"),
|
providers.CanUseSRV: providers.Can("SRV records with empty targets are not supported"),
|
||||||
providers.CanUseTLSA: providers.Can(),
|
providers.CanUseTLSA: providers.Can(),
|
||||||
providers.CanUseTXTMulti: providers.Can(),
|
|
||||||
providers.CantUseNOPURGE: providers.Can(),
|
providers.CantUseNOPURGE: providers.Can(),
|
||||||
providers.DocCreateDomains: providers.Can(),
|
providers.DocCreateDomains: providers.Can(),
|
||||||
providers.DocDualHost: providers.Can(),
|
providers.DocDualHost: providers.Can(),
|
||||||
@ -67,6 +66,10 @@ func newDsp(conf map[string]string, meta json.RawMessage) (providers.DNSServiceP
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
fns := providers.DspFuncs{
|
||||||
|
Initializer: newDsp,
|
||||||
|
AuditRecordsor: AuditRecords,
|
||||||
|
}
|
||||||
providers.RegisterRegistrarType("HEXONET", newReg)
|
providers.RegisterRegistrarType("HEXONET", newReg)
|
||||||
providers.RegisterDomainServiceProviderType("HEXONET", newDsp, features)
|
providers.RegisterDomainServiceProviderType("HEXONET", fns, features)
|
||||||
}
|
}
|
||||||
|
11
providers/internetbs/auditrecords.go
Normal file
11
providers/internetbs/auditrecords.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package internetbs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
33
providers/inwx/auditrecords.go
Normal file
33
providers/inwx/auditrecords.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package inwx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/pkg/recordaudit"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
|
||||||
|
if err := recordaudit.TxtNoBackticks(records); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Still needed as of 2021-03-01
|
||||||
|
|
||||||
|
if err := recordaudit.TxtNoLen255(records); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Still needed as of 2021-03-01
|
||||||
|
|
||||||
|
if err := recordaudit.TxtNoTrailingSpace(records); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Still needed as of 2021-03-01
|
||||||
|
|
||||||
|
if err := recordaudit.TxtNotEmpty(records); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Still needed as of 2021-03-01
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/StackExchange/dnscontrol/v3/models"
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
|
||||||
"github.com/StackExchange/dnscontrol/v3/providers"
|
"github.com/StackExchange/dnscontrol/v3/providers"
|
||||||
|
|
||||||
"github.com/nrdcg/goinwx"
|
"github.com/nrdcg/goinwx"
|
||||||
@ -49,7 +50,6 @@ var features = providers.DocumentationNotes{
|
|||||||
providers.CanUseSRV: providers.Can("SRV records with empty targets are not supported."),
|
providers.CanUseSRV: providers.Can("SRV records with empty targets are not supported."),
|
||||||
providers.CanUseSSHFP: providers.Can(),
|
providers.CanUseSSHFP: providers.Can(),
|
||||||
providers.CanUseTLSA: providers.Can(),
|
providers.CanUseTLSA: providers.Can(),
|
||||||
providers.CanUseTXTMulti: providers.Can(),
|
|
||||||
providers.CanAutoDNSSEC: providers.Unimplemented("Supported by INWX but not implemented yet."),
|
providers.CanAutoDNSSEC: providers.Unimplemented("Supported by INWX but not implemented yet."),
|
||||||
providers.DocOfficiallySupported: providers.Cannot(),
|
providers.DocOfficiallySupported: providers.Cannot(),
|
||||||
providers.DocDualHost: providers.Can(),
|
providers.DocDualHost: providers.Can(),
|
||||||
@ -68,7 +68,11 @@ type inwxAPI struct {
|
|||||||
// init registers the registrar and the domain service provider with dnscontrol.
|
// init registers the registrar and the domain service provider with dnscontrol.
|
||||||
func init() {
|
func init() {
|
||||||
providers.RegisterRegistrarType("INWX", newInwxReg)
|
providers.RegisterRegistrarType("INWX", newInwxReg)
|
||||||
providers.RegisterDomainServiceProviderType("INWX", newInwxDsp, features)
|
fns := providers.DspFuncs{
|
||||||
|
Initializer: newInwxDsp,
|
||||||
|
AuditRecordsor: AuditRecords,
|
||||||
|
}
|
||||||
|
providers.RegisterDomainServiceProviderType("INWX", fns, features)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getOTP either returns the TOTPValue or uses TOTPKey and the current time to generate a valid TOTPValue.
|
// getOTP either returns the TOTPValue or uses TOTPKey and the current time to generate a valid TOTPValue.
|
||||||
@ -231,6 +235,7 @@ func (api *inwxAPI) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Cor
|
|||||||
}
|
}
|
||||||
|
|
||||||
models.PostProcessRecords(foundRecords)
|
models.PostProcessRecords(foundRecords)
|
||||||
|
txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
|
||||||
|
|
||||||
err = checkRecords(dc.Records)
|
err = checkRecords(dc.Records)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
11
providers/linode/auditrecords.go
Normal file
11
providers/linode/auditrecords.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package linode
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -93,7 +93,11 @@ var features = providers.DocumentationNotes{
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// SRV support is in this provider, but Linode doesn't seem to support it properly
|
// SRV support is in this provider, but Linode doesn't seem to support it properly
|
||||||
providers.RegisterDomainServiceProviderType("LINODE", NewLinode, features)
|
fns := providers.DspFuncs{
|
||||||
|
Initializer: NewLinode,
|
||||||
|
AuditRecordsor: AuditRecords,
|
||||||
|
}
|
||||||
|
providers.RegisterDomainServiceProviderType("LINODE", fns, features)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNameservers returns the nameservers for a domain.
|
// GetNameservers returns the nameservers for a domain.
|
||||||
|
43
providers/msdns/auditrecords.go
Normal file
43
providers/msdns/auditrecords.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package msdns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/pkg/recordaudit"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
|
||||||
|
if err := recordaudit.TxtNoMultipleStrings(records); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Still needed as of 2021-03-01
|
||||||
|
|
||||||
|
if err := recordaudit.TxtNotEmpty(records); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Still needed as of 2021-03-01
|
||||||
|
|
||||||
|
if err := recordaudit.TxtNoBackticks(records); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Still needed as of 2021-03-01
|
||||||
|
|
||||||
|
if err := recordaudit.TxtNoDoubleQuotes(records); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Still needed as of 2021-03-01
|
||||||
|
|
||||||
|
if err := recordaudit.TxtNoSingleQuotes(records); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Still needed as of 2021-03-01
|
||||||
|
|
||||||
|
if err := recordaudit.TxtNoLen255(records); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Still needed as of 2021-03-01
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/StackExchange/dnscontrol/v3/models"
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetDomainCorrections gets existing records, diffs them against existing, and returns corrections.
|
// GetDomainCorrections gets existing records, diffs them against existing, and returns corrections.
|
||||||
@ -18,6 +19,7 @@ func (c *msdnsProvider) GenerateDomainCorrections(dc *models.DomainConfig, exist
|
|||||||
|
|
||||||
// Normalize
|
// Normalize
|
||||||
models.PostProcessRecords(foundRecords)
|
models.PostProcessRecords(foundRecords)
|
||||||
|
txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
|
||||||
|
|
||||||
differ := diff.New(dc)
|
differ := diff.New(dc)
|
||||||
_, creates, dels, modifications, err := differ.IncrementalDiff(foundRecords)
|
_, creates, dels, modifications, err := differ.IncrementalDiff(foundRecords)
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"github.com/StackExchange/dnscontrol/v3/models"
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
|
||||||
"github.com/StackExchange/dnscontrol/v3/providers"
|
"github.com/StackExchange/dnscontrol/v3/providers"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -24,7 +25,6 @@ var features = providers.DocumentationNotes{
|
|||||||
providers.CanUsePTR: providers.Can(),
|
providers.CanUsePTR: providers.Can(),
|
||||||
providers.CanUseSRV: providers.Can(),
|
providers.CanUseSRV: providers.Can(),
|
||||||
providers.CanUseTLSA: providers.Unimplemented(),
|
providers.CanUseTLSA: providers.Unimplemented(),
|
||||||
providers.CanUseTXTMulti: providers.Unimplemented(),
|
|
||||||
providers.DocCreateDomains: providers.Cannot("This provider assumes the zone already existing on the dns server"),
|
providers.DocCreateDomains: providers.Cannot("This provider assumes the zone already existing on the dns server"),
|
||||||
providers.DocDualHost: providers.Cannot("This driver does not manage NS records, so should not be used for dual-host scenarios"),
|
providers.DocDualHost: providers.Cannot("This driver does not manage NS records, so should not be used for dual-host scenarios"),
|
||||||
providers.DocOfficiallySupported: providers.Can(),
|
providers.DocOfficiallySupported: providers.Can(),
|
||||||
@ -33,7 +33,11 @@ var features = providers.DocumentationNotes{
|
|||||||
// Register with the dnscontrol system.
|
// Register with the dnscontrol system.
|
||||||
// This establishes the name (all caps), and the function to call to initialize it.
|
// This establishes the name (all caps), and the function to call to initialize it.
|
||||||
func init() {
|
func init() {
|
||||||
providers.RegisterDomainServiceProviderType("MSDNS", newDNS, features)
|
fns := providers.DspFuncs{
|
||||||
|
Initializer: newDNS,
|
||||||
|
AuditRecordsor: AuditRecords,
|
||||||
|
}
|
||||||
|
providers.RegisterDomainServiceProviderType("MSDNS", fns, features)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDNS(config map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
|
func newDNS(config map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
|
||||||
@ -76,6 +80,8 @@ func (client *msdnsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*m
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
models.PostProcessRecords(existing)
|
models.PostProcessRecords(existing)
|
||||||
|
txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
|
||||||
|
|
||||||
clean := PrepFoundRecords(existing)
|
clean := PrepFoundRecords(existing)
|
||||||
PrepDesiredRecords(dc)
|
PrepDesiredRecords(dc)
|
||||||
return client.GenerateDomainCorrections(dc, clean)
|
return client.GenerateDomainCorrections(dc, clean)
|
||||||
|
@ -162,6 +162,8 @@ func generatePSDelete(dnsserver, domain string, rec *models.RecordConfig) string
|
|||||||
fmt.Fprintf(&b, ` -RRType "%s"`, rec.Type)
|
fmt.Fprintf(&b, ` -RRType "%s"`, rec.Type)
|
||||||
if rec.Type == "MX" {
|
if rec.Type == "MX" {
|
||||||
fmt.Fprintf(&b, ` -RecordData %d,"%s"`, rec.MxPreference, rec.GetTargetField())
|
fmt.Fprintf(&b, ` -RecordData %d,"%s"`, rec.MxPreference, rec.GetTargetField())
|
||||||
|
} else if rec.Type == "TXT" {
|
||||||
|
fmt.Fprintf(&b, ` -RecordData %s`, rec.GetTargetField())
|
||||||
} else if rec.Type == "SRV" {
|
} else if rec.Type == "SRV" {
|
||||||
// https://www.gitmemory.com/issue/MicrosoftDocs/windows-powershell-docs/1149/511916884
|
// https://www.gitmemory.com/issue/MicrosoftDocs/windows-powershell-docs/1149/511916884
|
||||||
fmt.Fprintf(&b, ` -RecordData %d,%d,%d,"%s"`, rec.SrvPriority, rec.SrvWeight, rec.SrvPort, rec.GetTargetField())
|
fmt.Fprintf(&b, ` -RecordData %d,%d,%d,"%s"`, rec.SrvPriority, rec.SrvWeight, rec.SrvPort, rec.GetTargetField())
|
||||||
@ -214,7 +216,9 @@ func generatePSCreate(dnsserver, domain string, rec *models.RecordConfig) string
|
|||||||
//case "WKS":
|
//case "WKS":
|
||||||
// fmt.Fprintf(&b, ` -Wks -InternetAddress <IPAddress> -InternetProtocol {UDP | TCP} -Service <String[]>`, rec.GetTargetField())
|
// fmt.Fprintf(&b, ` -Wks -InternetAddress <IPAddress> -InternetProtocol {UDP | TCP} -Service <String[]>`, rec.GetTargetField())
|
||||||
case "TXT":
|
case "TXT":
|
||||||
fmt.Fprintf(&b, ` -Txt -DescriptiveText "%s"`, rec.GetTargetField())
|
fmt.Printf("DEBUG TXT len = %v\n", rec.TxtStrings)
|
||||||
|
fmt.Printf("DEBUG TXT target = %q\n", rec.GetTargetField())
|
||||||
|
fmt.Fprintf(&b, ` -Txt -DescriptiveText %s`, rec.GetTargetField())
|
||||||
//case "RT":
|
//case "RT":
|
||||||
// fmt.Fprintf(&b, ` -RT -IntermediateHost <String> -Preference <UInt16>`, rec.GetTargetField())
|
// fmt.Fprintf(&b, ` -RT -IntermediateHost <String> -Preference <UInt16>`, rec.GetTargetField())
|
||||||
//case "RP":
|
//case "RP":
|
||||||
|
11
providers/namecheap/auditrecords.go
Normal file
11
providers/namecheap/auditrecords.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package namecheap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -41,7 +41,11 @@ var features = providers.DocumentationNotes{
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
providers.RegisterRegistrarType("NAMECHEAP", newReg)
|
providers.RegisterRegistrarType("NAMECHEAP", newReg)
|
||||||
providers.RegisterDomainServiceProviderType("NAMECHEAP", newDsp, features)
|
fns := providers.DspFuncs{
|
||||||
|
Initializer: newDsp,
|
||||||
|
AuditRecordsor: AuditRecords,
|
||||||
|
}
|
||||||
|
providers.RegisterDomainServiceProviderType("NAMECHEAP", fns, features)
|
||||||
providers.RegisterCustomRecordType("URL", "NAMECHEAP", "")
|
providers.RegisterCustomRecordType("URL", "NAMECHEAP", "")
|
||||||
providers.RegisterCustomRecordType("URL301", "NAMECHEAP", "")
|
providers.RegisterCustomRecordType("URL301", "NAMECHEAP", "")
|
||||||
providers.RegisterCustomRecordType("FRAME", "NAMECHEAP", "")
|
providers.RegisterCustomRecordType("FRAME", "NAMECHEAP", "")
|
||||||
|
41
providers/namedotcom/auditrecords.go
Normal file
41
providers/namedotcom/auditrecords.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package namedotcom
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/pkg/recordaudit"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
|
||||||
|
if err := MaxLengthNDC(records); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Still needed as of 2021-03-01
|
||||||
|
|
||||||
|
if err := recordaudit.TxtNotEmpty(records); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Still needed as of 2021-03-01
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxLengthNDC returns and error if the sum of the strings
|
||||||
|
// are longer than permitted by DigitalOcean. Sadly their
|
||||||
|
// length limit is undocumented. This seems to work.
|
||||||
|
func MaxLengthNDC(records []*models.RecordConfig) error {
|
||||||
|
for _, rc := range records {
|
||||||
|
|
||||||
|
if rc.HasFormatIdenticalToTXT() { // TXT and similar:
|
||||||
|
if len(rc.GetTargetField()) > 512 {
|
||||||
|
return fmt.Errorf("encoded txt too long")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -24,7 +24,6 @@ var features = providers.DocumentationNotes{
|
|||||||
providers.CanUseAlias: providers.Can(),
|
providers.CanUseAlias: providers.Can(),
|
||||||
providers.CanUsePTR: providers.Cannot("PTR records are not supported (See Link)", "https://www.name.com/support/articles/205188508-Reverse-DNS-records"),
|
providers.CanUsePTR: providers.Cannot("PTR records are not supported (See Link)", "https://www.name.com/support/articles/205188508-Reverse-DNS-records"),
|
||||||
providers.CanUseSRV: providers.Can("SRV records with empty targets are not supported"),
|
providers.CanUseSRV: providers.Can("SRV records with empty targets are not supported"),
|
||||||
providers.CanUseTXTMulti: providers.Cannot(),
|
|
||||||
providers.DocCreateDomains: providers.Cannot("New domains require registration"),
|
providers.DocCreateDomains: providers.Cannot("New domains require registration"),
|
||||||
providers.DocDualHost: providers.Cannot("Apex NS records not editable"),
|
providers.DocDualHost: providers.Cannot("Apex NS records not editable"),
|
||||||
providers.DocOfficiallySupported: providers.Can(),
|
providers.DocOfficiallySupported: providers.Can(),
|
||||||
@ -56,5 +55,9 @@ func newProvider(conf map[string]string) (*namedotcomProvider, error) {
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
providers.RegisterRegistrarType("NAMEDOTCOM", newReg)
|
providers.RegisterRegistrarType("NAMEDOTCOM", newReg)
|
||||||
providers.RegisterDomainServiceProviderType("NAMEDOTCOM", newDsp, features)
|
fns := providers.DspFuncs{
|
||||||
|
Initializer: newDsp,
|
||||||
|
AuditRecordsor: AuditRecords,
|
||||||
|
}
|
||||||
|
providers.RegisterDomainServiceProviderType("NAMEDOTCOM", fns, features)
|
||||||
}
|
}
|
||||||
|
@ -170,8 +170,8 @@ func (n *namedotcomProvider) createRecord(rc *models.RecordConfig, domain string
|
|||||||
switch rc.Type { // #rtype_variations
|
switch rc.Type { // #rtype_variations
|
||||||
case "A", "AAAA", "ANAME", "CNAME", "MX", "NS":
|
case "A", "AAAA", "ANAME", "CNAME", "MX", "NS":
|
||||||
// nothing
|
// nothing
|
||||||
case "TXT":
|
case "TXT":
|
||||||
record.Answer = encodeTxt(rc.TxtStrings)
|
// record.Answer = encodeTxt(rc.TxtStrings)
|
||||||
case "SRV":
|
case "SRV":
|
||||||
if rc.GetTargetField() == "." {
|
if rc.GetTargetField() == "." {
|
||||||
return errors.New("SRV records with empty targets are not supported (as of 2019-11-05, the API returns 'Parameter Value Error - Invalid Srv Format')")
|
return errors.New("SRV records with empty targets are not supported (as of 2019-11-05, the API returns 'Parameter Value Error - Invalid Srv Format')")
|
||||||
@ -187,18 +187,18 @@ func (n *namedotcomProvider) createRecord(rc *models.RecordConfig, domain string
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeTxt encodes TxtStrings for sending in the CREATE/MODIFY API:
|
// // makeTxt encodes TxtStrings for sending in the CREATE/MODIFY API:
|
||||||
func encodeTxt(txts []string) string {
|
// func encodeTxt(txts []string) string {
|
||||||
ans := txts[0]
|
// ans := txts[0]
|
||||||
|
|
||||||
if len(txts) > 1 {
|
// if len(txts) > 1 {
|
||||||
ans = ""
|
// ans = ""
|
||||||
for _, t := range txts {
|
// for _, t := range txts {
|
||||||
ans += `"` + strings.Replace(t, `"`, `\"`, -1) + `"`
|
// ans += `"` + strings.Replace(t, `"`, `\"`, -1) + `"`
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return ans
|
// return ans
|
||||||
}
|
// }
|
||||||
|
|
||||||
// finds a string surrounded by quotes that might contain an escaped quote character.
|
// finds a string surrounded by quotes that might contain an escaped quote character.
|
||||||
var quotedStringRegexp = regexp.MustCompile(`"((?:[^"\\]|\\.)*)"`)
|
var quotedStringRegexp = regexp.MustCompile(`"((?:[^"\\]|\\.)*)"`)
|
||||||
|
@ -21,16 +21,16 @@ var txtData = []struct {
|
|||||||
{[]string{"eh", "bzz", "cee"}, `"eh""bzz""cee"`},
|
{[]string{"eh", "bzz", "cee"}, `"eh""bzz""cee"`},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEncodeTxt(t *testing.T) {
|
// func TestEncodeTxt(t *testing.T) {
|
||||||
// Test encoded the lists of strings into a string:
|
// // Test encoded the lists of strings into a string:
|
||||||
for i, test := range txtData {
|
// for i, test := range txtData {
|
||||||
enc := encodeTxt(test.decoded)
|
// enc := encodeTxt(test.decoded)
|
||||||
if enc != test.encoded {
|
// if enc != test.encoded {
|
||||||
t.Errorf("%v: txt\n data: []string{%v}\nexpected: %s\n got: %s",
|
// t.Errorf("%v: txt\n data: []string{%v}\nexpected: %s\n got: %s",
|
||||||
i, "`"+strings.Join(test.decoded, "`, `")+"`", test.encoded, enc)
|
// i, "`"+strings.Join(test.decoded, "`, `")+"`", test.encoded, enc)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
func TestDecodeTxt(t *testing.T) {
|
func TestDecodeTxt(t *testing.T) {
|
||||||
// Test decoded a string into the list of strings:
|
// Test decoded a string into the list of strings:
|
||||||
|
13
providers/netcup/auditrecords.go
Normal file
13
providers/netcup/auditrecords.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package netcup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/pkg/recordaudit"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
return recordaudit.TxtNotEmpty(records)
|
||||||
|
// Still needed as of 2021-03-01
|
||||||
|
}
|
@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/StackExchange/dnscontrol/v3/models"
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
|
||||||
"github.com/StackExchange/dnscontrol/v3/providers"
|
"github.com/StackExchange/dnscontrol/v3/providers"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,12 +17,15 @@ var features = providers.DocumentationNotes{
|
|||||||
providers.CanUsePTR: providers.Cannot(),
|
providers.CanUsePTR: providers.Cannot(),
|
||||||
providers.CanUseSRV: providers.Can(),
|
providers.CanUseSRV: providers.Can(),
|
||||||
providers.CanUseCAA: providers.Can(),
|
providers.CanUseCAA: providers.Can(),
|
||||||
providers.CanUseTXTMulti: providers.Can(),
|
|
||||||
providers.CanGetZones: providers.Cannot(),
|
providers.CanGetZones: providers.Cannot(),
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
providers.RegisterDomainServiceProviderType("NETCUP", New, features)
|
fns := providers.DspFuncs{
|
||||||
|
Initializer: New,
|
||||||
|
AuditRecordsor: AuditRecords,
|
||||||
|
}
|
||||||
|
providers.RegisterDomainServiceProviderType("NETCUP", fns, features)
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new API handle.
|
// New creates a new API handle.
|
||||||
@ -94,6 +98,8 @@ func (api *netcupProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mod
|
|||||||
|
|
||||||
// Normalize
|
// Normalize
|
||||||
models.PostProcessRecords(existingRecords)
|
models.PostProcessRecords(existingRecords)
|
||||||
|
txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
|
||||||
|
|
||||||
differ := diff.New(dc)
|
differ := diff.New(dc)
|
||||||
_, create, del, modify, err := differ.IncrementalDiff(existingRecords)
|
_, create, del, modify, err := differ.IncrementalDiff(existingRecords)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
18
providers/ns1/auditrecords.go
Normal file
18
providers/ns1/auditrecords.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package ns1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/pkg/recordaudit"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
|
||||||
|
if err := recordaudit.TxtNoMultipleStrings(records); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Still needed as of 2021-03-01
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -18,14 +18,17 @@ import (
|
|||||||
var docNotes = providers.DocumentationNotes{
|
var docNotes = providers.DocumentationNotes{
|
||||||
providers.CanUseAlias: providers.Can(),
|
providers.CanUseAlias: providers.Can(),
|
||||||
providers.CanUsePTR: providers.Can(),
|
providers.CanUsePTR: providers.Can(),
|
||||||
providers.CanUseTXTMulti: providers.Cannot(),
|
|
||||||
providers.DocCreateDomains: providers.Cannot(),
|
providers.DocCreateDomains: providers.Cannot(),
|
||||||
providers.DocOfficiallySupported: providers.Cannot(),
|
providers.DocOfficiallySupported: providers.Cannot(),
|
||||||
providers.DocDualHost: providers.Can(),
|
providers.DocDualHost: providers.Can(),
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
providers.RegisterDomainServiceProviderType("NS1", newProvider, providers.CanUseSRV, docNotes)
|
fns := providers.DspFuncs{
|
||||||
|
Initializer: newProvider,
|
||||||
|
AuditRecordsor: AuditRecords,
|
||||||
|
}
|
||||||
|
providers.RegisterDomainServiceProviderType("NS1", fns, providers.CanUseSRV, docNotes)
|
||||||
providers.RegisterCustomRecordType("NS1_URLFWD", "NS1", "URLFWD")
|
providers.RegisterCustomRecordType("NS1_URLFWD", "NS1", "URLFWD")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
11
providers/octodns/auditrecords.go
Normal file
11
providers/octodns/auditrecords.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package octodns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -29,6 +29,7 @@ import (
|
|||||||
|
|
||||||
"github.com/StackExchange/dnscontrol/v3/models"
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
|
||||||
"github.com/StackExchange/dnscontrol/v3/providers"
|
"github.com/StackExchange/dnscontrol/v3/providers"
|
||||||
"github.com/StackExchange/dnscontrol/v3/providers/octodns/octoyaml"
|
"github.com/StackExchange/dnscontrol/v3/providers/octodns/octoyaml"
|
||||||
)
|
)
|
||||||
@ -37,7 +38,6 @@ var features = providers.DocumentationNotes{
|
|||||||
//providers.CanUseCAA: providers.Can(),
|
//providers.CanUseCAA: providers.Can(),
|
||||||
providers.CanUsePTR: providers.Can(),
|
providers.CanUsePTR: providers.Can(),
|
||||||
providers.CanUseSRV: providers.Can(),
|
providers.CanUseSRV: providers.Can(),
|
||||||
//providers.CanUseTXTMulti: providers.Can(),
|
|
||||||
providers.DocCreateDomains: providers.Cannot("Driver just maintains list of OctoDNS config files. You must manually create the master config files that refer these."),
|
providers.DocCreateDomains: providers.Cannot("Driver just maintains list of OctoDNS config files. You must manually create the master config files that refer these."),
|
||||||
providers.DocDualHost: providers.Cannot("Research is needed."),
|
providers.DocDualHost: providers.Cannot("Research is needed."),
|
||||||
providers.CanGetZones: providers.Unimplemented(),
|
providers.CanGetZones: providers.Unimplemented(),
|
||||||
@ -63,7 +63,11 @@ func initProvider(config map[string]string, providermeta json.RawMessage) (provi
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
providers.RegisterDomainServiceProviderType("OCTODNS", initProvider, features)
|
fns := providers.DspFuncs{
|
||||||
|
Initializer: initProvider,
|
||||||
|
AuditRecordsor: AuditRecords,
|
||||||
|
}
|
||||||
|
providers.RegisterDomainServiceProviderType("OCTODNS", fns, features)
|
||||||
}
|
}
|
||||||
|
|
||||||
// octodnsProvider is the provider handle for the OctoDNS driver.
|
// octodnsProvider is the provider handle for the OctoDNS driver.
|
||||||
@ -123,6 +127,7 @@ func (c *octodnsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode
|
|||||||
|
|
||||||
// Normalize
|
// Normalize
|
||||||
models.PostProcessRecords(foundRecords)
|
models.PostProcessRecords(foundRecords)
|
||||||
|
txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
|
||||||
|
|
||||||
differ := diff.New(dc)
|
differ := diff.New(dc)
|
||||||
_, create, del, mod, err := differ.IncrementalDiff(foundRecords)
|
_, create, del, mod, err := differ.IncrementalDiff(foundRecords)
|
||||||
|
11
providers/opensrs/auditrecords.go
Normal file
11
providers/opensrs/auditrecords.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package opensrs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
11
providers/oracle/auditrecords.go
Normal file
11
providers/oracle/auditrecords.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package oracle
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/StackExchange/dnscontrol/v3/models"
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
||||||
"github.com/StackExchange/dnscontrol/v3/pkg/printer"
|
"github.com/StackExchange/dnscontrol/v3/pkg/printer"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
|
||||||
"github.com/StackExchange/dnscontrol/v3/providers"
|
"github.com/StackExchange/dnscontrol/v3/providers"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -31,11 +32,14 @@ var features = providers.DocumentationNotes{
|
|||||||
providers.CanUseSRV: providers.Can(),
|
providers.CanUseSRV: providers.Can(),
|
||||||
providers.CanUseSSHFP: providers.Can(),
|
providers.CanUseSSHFP: providers.Can(),
|
||||||
providers.CanUseTLSA: providers.Can(),
|
providers.CanUseTLSA: providers.Can(),
|
||||||
providers.CanUseTXTMulti: providers.Can(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
providers.RegisterDomainServiceProviderType("ORACLE", New, features)
|
fns := providers.DspFuncs{
|
||||||
|
Initializer: New,
|
||||||
|
AuditRecordsor: AuditRecords,
|
||||||
|
}
|
||||||
|
providers.RegisterDomainServiceProviderType("ORACLE", fns, features)
|
||||||
}
|
}
|
||||||
|
|
||||||
type oracleProvider struct {
|
type oracleProvider struct {
|
||||||
@ -218,6 +222,7 @@ func (o *oracleProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*model
|
|||||||
|
|
||||||
// Normalize
|
// Normalize
|
||||||
models.PostProcessRecords(existingRecords)
|
models.PostProcessRecords(existingRecords)
|
||||||
|
txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
|
||||||
|
|
||||||
filteredNewRecords := models.Records{}
|
filteredNewRecords := models.Records{}
|
||||||
|
|
||||||
|
11
providers/ovh/auditrecords.go
Normal file
11
providers/ovh/auditrecords.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package ovh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/StackExchange/dnscontrol/v3/models"
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
|
||||||
"github.com/StackExchange/dnscontrol/v3/providers"
|
"github.com/StackExchange/dnscontrol/v3/providers"
|
||||||
"github.com/ovh/go-ovh/ovh"
|
"github.com/ovh/go-ovh/ovh"
|
||||||
)
|
)
|
||||||
@ -28,7 +29,6 @@ var features = providers.DocumentationNotes{
|
|||||||
providers.DocDualHost: providers.Can(),
|
providers.DocDualHost: providers.Can(),
|
||||||
providers.DocOfficiallySupported: providers.Cannot(),
|
providers.DocOfficiallySupported: providers.Cannot(),
|
||||||
providers.CanGetZones: providers.Can(),
|
providers.CanGetZones: providers.Can(),
|
||||||
providers.CanUseTXTMulti: providers.Can(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newOVH(m map[string]string, metadata json.RawMessage) (*ovhProvider, error) {
|
func newOVH(m map[string]string, metadata json.RawMessage) (*ovhProvider, error) {
|
||||||
@ -55,8 +55,12 @@ func newReg(conf map[string]string) (providers.Registrar, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
fns := providers.DspFuncs{
|
||||||
|
Initializer: newDsp,
|
||||||
|
AuditRecordsor: AuditRecords,
|
||||||
|
}
|
||||||
providers.RegisterRegistrarType("OVH", newReg)
|
providers.RegisterRegistrarType("OVH", newReg)
|
||||||
providers.RegisterDomainServiceProviderType("OVH", newDsp, features)
|
providers.RegisterDomainServiceProviderType("OVH", fns, features)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ovhProvider) GetNameservers(domain string) ([]*models.Nameserver, error) {
|
func (c *ovhProvider) GetNameservers(domain string) ([]*models.Nameserver, error) {
|
||||||
@ -116,6 +120,7 @@ func (c *ovhProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.C
|
|||||||
|
|
||||||
// Normalize
|
// Normalize
|
||||||
models.PostProcessRecords(actual)
|
models.PostProcessRecords(actual)
|
||||||
|
txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
|
||||||
|
|
||||||
differ := diff.New(dc)
|
differ := diff.New(dc)
|
||||||
_, create, delete, modify, err := differ.IncrementalDiff(actual)
|
_, create, delete, modify, err := differ.IncrementalDiff(actual)
|
||||||
|
11
providers/powerdns/auditrecords.go
Normal file
11
providers/powerdns/auditrecords.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package powerdns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -27,13 +27,16 @@ var features = providers.DocumentationNotes{
|
|||||||
providers.DocCreateDomains: providers.Can(),
|
providers.DocCreateDomains: providers.Can(),
|
||||||
providers.DocOfficiallySupported: providers.Cannot(),
|
providers.DocOfficiallySupported: providers.Cannot(),
|
||||||
providers.CanGetZones: providers.Can(),
|
providers.CanGetZones: providers.Can(),
|
||||||
providers.CanUseTXTMulti: providers.Can(),
|
|
||||||
providers.DocDualHost: providers.Can(),
|
providers.DocDualHost: providers.Can(),
|
||||||
providers.CanUseNAPTR: providers.Can(),
|
providers.CanUseNAPTR: providers.Can(),
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
providers.RegisterDomainServiceProviderType("POWERDNS", NewProvider, features)
|
fns := providers.DspFuncs{
|
||||||
|
Initializer: NewProvider,
|
||||||
|
AuditRecordsor: AuditRecords,
|
||||||
|
}
|
||||||
|
providers.RegisterDomainServiceProviderType("POWERDNS", fns, features)
|
||||||
}
|
}
|
||||||
|
|
||||||
// powerdnsProvider represents the powerdnsProvider DNSServiceProvider.
|
// powerdnsProvider represents the powerdnsProvider DNSServiceProvider.
|
||||||
|
@ -40,8 +40,19 @@ var RegistrarTypes = map[string]RegistrarInitializer{}
|
|||||||
// DspInitializer is a function to create a DNS service provider. Function will be passed the unprocessed json payload from the configuration file for the given provider.
|
// DspInitializer is a function to create a DNS service provider. Function will be passed the unprocessed json payload from the configuration file for the given provider.
|
||||||
type DspInitializer func(map[string]string, json.RawMessage) (DNSServiceProvider, error)
|
type DspInitializer func(map[string]string, json.RawMessage) (DNSServiceProvider, error)
|
||||||
|
|
||||||
|
// AuditRecordsor is a function that verifies that all the records
|
||||||
|
// are supportable by this provider. It returns an error related to
|
||||||
|
// the first record that this provider can not support.
|
||||||
|
type AuditRecordsor func([]*models.RecordConfig) error
|
||||||
|
|
||||||
|
// DspFuncs lists functions registered with a provider.
|
||||||
|
type DspFuncs struct {
|
||||||
|
Initializer DspInitializer
|
||||||
|
AuditRecordsor AuditRecordsor
|
||||||
|
}
|
||||||
|
|
||||||
// DNSProviderTypes stores initializer for each DSP.
|
// DNSProviderTypes stores initializer for each DSP.
|
||||||
var DNSProviderTypes = map[string]DspInitializer{}
|
var DNSProviderTypes = map[string]DspFuncs{}
|
||||||
|
|
||||||
// RegisterRegistrarType adds a registrar type to the registry by providing a suitable initialization function.
|
// RegisterRegistrarType adds a registrar type to the registry by providing a suitable initialization function.
|
||||||
func RegisterRegistrarType(name string, init RegistrarInitializer, pm ...ProviderMetadata) {
|
func RegisterRegistrarType(name string, init RegistrarInitializer, pm ...ProviderMetadata) {
|
||||||
@ -53,11 +64,11 @@ func RegisterRegistrarType(name string, init RegistrarInitializer, pm ...Provide
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RegisterDomainServiceProviderType adds a dsp to the registry with the given initialization function.
|
// RegisterDomainServiceProviderType adds a dsp to the registry with the given initialization function.
|
||||||
func RegisterDomainServiceProviderType(name string, init DspInitializer, pm ...ProviderMetadata) {
|
func RegisterDomainServiceProviderType(name string, fns DspFuncs, pm ...ProviderMetadata) {
|
||||||
if _, ok := DNSProviderTypes[name]; ok {
|
if _, ok := DNSProviderTypes[name]; ok {
|
||||||
log.Fatalf("Cannot register registrar type %s multiple times", name)
|
log.Fatalf("Cannot register registrar type %s multiple times", name)
|
||||||
}
|
}
|
||||||
DNSProviderTypes[name] = init
|
DNSProviderTypes[name] = fns
|
||||||
unwrapProviderCapabilities(name, pm)
|
unwrapProviderCapabilities(name, pm)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,11 +83,22 @@ func CreateRegistrar(rType string, config map[string]string) (Registrar, error)
|
|||||||
|
|
||||||
// CreateDNSProvider initializes a dns provider instance from given credentials.
|
// CreateDNSProvider initializes a dns provider instance from given credentials.
|
||||||
func CreateDNSProvider(dType string, config map[string]string, meta json.RawMessage) (DNSServiceProvider, error) {
|
func CreateDNSProvider(dType string, config map[string]string, meta json.RawMessage) (DNSServiceProvider, error) {
|
||||||
initer, ok := DNSProviderTypes[dType]
|
p, ok := DNSProviderTypes[dType]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("DSP type %s not declared", dType)
|
return nil, fmt.Errorf("DSP type %s not declared", dType)
|
||||||
}
|
}
|
||||||
return initer(config, meta)
|
return p.Initializer(config, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func AuditRecords(dType string, rcs models.Records) error {
|
||||||
|
p, ok := DNSProviderTypes[dType]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("DSP type %s not declared", dType)
|
||||||
|
}
|
||||||
|
if p.AuditRecordsor == nil {
|
||||||
|
return fmt.Errorf("DSP type %s has no AuditRecordsor", dType)
|
||||||
|
}
|
||||||
|
return p.AuditRecordsor(rcs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// None is a basic provider type that does absolutely nothing. Can be useful as a placeholder for third parties or unimplemented providers.
|
// None is a basic provider type that does absolutely nothing. Can be useful as a placeholder for third parties or unimplemented providers.
|
||||||
|
11
providers/route53/auditrecords.go
Normal file
11
providers/route53/auditrecords.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package route53
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -17,6 +17,7 @@ import (
|
|||||||
|
|
||||||
"github.com/StackExchange/dnscontrol/v3/models"
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
|
||||||
"github.com/StackExchange/dnscontrol/v3/providers"
|
"github.com/StackExchange/dnscontrol/v3/providers"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -72,14 +73,17 @@ var features = providers.DocumentationNotes{
|
|||||||
providers.DocOfficiallySupported: providers.Can(),
|
providers.DocOfficiallySupported: providers.Can(),
|
||||||
providers.CanUsePTR: providers.Can(),
|
providers.CanUsePTR: providers.Can(),
|
||||||
providers.CanUseSRV: providers.Can(),
|
providers.CanUseSRV: providers.Can(),
|
||||||
providers.CanUseTXTMulti: providers.Can(),
|
|
||||||
providers.CanUseCAA: providers.Can(),
|
providers.CanUseCAA: providers.Can(),
|
||||||
providers.CanUseRoute53Alias: providers.Can(),
|
providers.CanUseRoute53Alias: providers.Can(),
|
||||||
providers.CanGetZones: providers.Can(),
|
providers.CanGetZones: providers.Can(),
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
providers.RegisterDomainServiceProviderType("ROUTE53", newRoute53Dsp, features)
|
fns := providers.DspFuncs{
|
||||||
|
Initializer: newRoute53Dsp,
|
||||||
|
AuditRecordsor: AuditRecords,
|
||||||
|
}
|
||||||
|
providers.RegisterDomainServiceProviderType("ROUTE53", fns, features)
|
||||||
providers.RegisterRegistrarType("ROUTE53", newRoute53Reg)
|
providers.RegisterRegistrarType("ROUTE53", newRoute53Reg)
|
||||||
providers.RegisterCustomRecordType("R53_ALIAS", "ROUTE53", "")
|
providers.RegisterCustomRecordType("R53_ALIAS", "ROUTE53", "")
|
||||||
}
|
}
|
||||||
@ -232,6 +236,7 @@ func (r *route53Provider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode
|
|||||||
|
|
||||||
// Normalize
|
// Normalize
|
||||||
models.PostProcessRecords(existingRecords)
|
models.PostProcessRecords(existingRecords)
|
||||||
|
txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
|
||||||
|
|
||||||
// diff
|
// diff
|
||||||
differ := diff.New(dc, getAliasMap)
|
differ := diff.New(dc, getAliasMap)
|
||||||
|
11
providers/softlayer/auditrecords.go
Normal file
11
providers/softlayer/auditrecords.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package softlayer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -27,7 +27,11 @@ var features = providers.DocumentationNotes{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
providers.RegisterDomainServiceProviderType("SOFTLAYER", newReg, features)
|
fns := providers.DspFuncs{
|
||||||
|
Initializer: newReg,
|
||||||
|
AuditRecordsor: AuditRecords,
|
||||||
|
}
|
||||||
|
providers.RegisterDomainServiceProviderType("SOFTLAYER", fns, features)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newReg(conf map[string]string, _ json.RawMessage) (providers.DNSServiceProvider, error) {
|
func newReg(conf map[string]string, _ json.RawMessage) (providers.DNSServiceProvider, error) {
|
||||||
|
20
providers/vultr/auditrecords.go
Normal file
20
providers/vultr/auditrecords.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package vultr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/pkg/recordaudit"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns an error if any records are not
|
||||||
|
// supportable by this provider.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) error {
|
||||||
|
|
||||||
|
// TODO(tlim) Needs investigation. Could be a dnscontrol issue or
|
||||||
|
// the provider doesn't support double quotes.
|
||||||
|
if err := recordaudit.TxtNoDoubleQuotes(records); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Still needed as of 2021-03-02
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -37,7 +37,11 @@ var features = providers.DocumentationNotes{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
providers.RegisterDomainServiceProviderType("VULTR", NewProvider, features)
|
fns := providers.DspFuncs{
|
||||||
|
Initializer: NewProvider,
|
||||||
|
AuditRecordsor: AuditRecords,
|
||||||
|
}
|
||||||
|
providers.RegisterDomainServiceProviderType("VULTR", fns, features)
|
||||||
}
|
}
|
||||||
|
|
||||||
// vultrProvider represents the Vultr DNSServiceProvider.
|
// vultrProvider represents the Vultr DNSServiceProvider.
|
||||||
@ -220,10 +224,6 @@ func toVultrRecord(dc *models.DomainConfig, rc *models.RecordConfig, vultrID int
|
|||||||
if strings.HasSuffix(data, ".") {
|
if strings.HasSuffix(data, ".") {
|
||||||
data = data[:len(data)-1]
|
data = data[:len(data)-1]
|
||||||
}
|
}
|
||||||
// Vultr needs TXT record in quotes.
|
|
||||||
if rc.Type == "TXT" {
|
|
||||||
data = fmt.Sprintf(`"%s"`, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
var priority *int
|
var priority *int
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user