diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4ed646f57..f2c85cc16 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,8 +31,6 @@ jobs: fail-fast: false matrix: provider: [AZURE_DNS, BIND, CLOUDFLAREAPI, DIGITALOCEAN, GANDI_V5, GCLOUD, HEXONET, NAMEDOTCOM, ROUTE53] - exclude: - - provider: DIGITALOCEAN # remove when #636 is fixed steps: - name: Checkout repo uses: actions/checkout@v2 diff --git a/docs/_includes/matrix.html b/docs/_includes/matrix.html index bea0e6491..0dbc07c41 100644 --- a/docs/_includes/matrix.html +++ b/docs/_includes/matrix.html @@ -472,8 +472,8 @@ - - + + @@ -888,7 +888,9 @@ - + + + diff --git a/docs/_providers/digitalocean.md b/docs/_providers/digitalocean.md index f7c4cb769..bc91f0624 100644 --- a/docs/_providers/digitalocean.md +++ b/docs/_providers/digitalocean.md @@ -38,5 +38,8 @@ D("example.tld", REG_NONE, DnsProvider(DIGITALOCEAN), ## Limitations -- Digialocean DNS doesn't support `;` value with CAA-records ([DigitalOcean documentation](https://www.digitalocean.com/docs/networking/dns/how-to/create-caa-records/)) -- No support for TXT records with multiple strings, as the API prevents espacing quotes. +- 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, + their implementation is lacking. It does not support strings that + include double-quotes nor many long strings. The length limits may + restrict your ability to use very long DKIM or SPF records. diff --git a/go.mod b/go.mod index f6f0cf321..4d461e4ee 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/billputer/go-namecheap v0.0.0-20170915210158-0c7adb0710f8 github.com/cenkalti/backoff v2.1.1+incompatible // indirect github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect - github.com/digitalocean/godo v1.46.0 + github.com/digitalocean/godo v1.52.0 github.com/dnsimple/dnsimple-go v0.62.0 github.com/exoscale/egoscale v0.23.0 github.com/go-acme/lego v2.7.2+incompatible @@ -54,12 +54,13 @@ require ( github.com/urfave/cli/v2 v2.2.0 github.com/vultr/govultr v1.0.0 golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 // indirect - golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0 - golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 + golang.org/x/net v0.0.0-20201110031124-69a78807bb2b + golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58 golang.org/x/sync v0.0.0-20201008141435-b3e1573b7520 // indirect golang.org/x/sys v0.0.0-20201018121011-98379d014ca7 // indirect golang.org/x/tools v0.0.0-20201017001424-6003fad69a88 // indirect google.golang.org/api v0.33.0 + google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20201015140912-32ed001d685c // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/ini.v1 v1.42.0 // indirect diff --git a/go.sum b/go.sum index bb9228655..156434711 100644 --- a/go.sum +++ b/go.sum @@ -105,8 +105,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/digitalocean/godo v1.46.0 h1:WRbwjATilgz2NE4NGMeSDpeicy9h4xSKNGuRJ/Nq/fA= -github.com/digitalocean/godo v1.46.0/go.mod h1:p7dOjjtSBqCTUksqtA5Fd3uaKs9kyTq2xcz76ulEJRU= +github.com/digitalocean/godo v1.52.0 h1:1QSUC0w5T1wS1d/1uvPtG8GLeD0p/4zhx1Q+Fxtna+k= +github.com/digitalocean/godo v1.52.0/go.mod h1:p7dOjjtSBqCTUksqtA5Fd3uaKs9kyTq2xcz76ulEJRU= github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dnsimple/dnsimple-go v0.62.0 h1:Lr4U8F08FBL90rMEtQMR547RebifgwTPGCRC/+6ZW4g= @@ -446,8 +446,8 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0 h1:5kGOVHlq0euqwzgTC9Vu15p6fV1Wi0ArVi8da2urnVg= -golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= @@ -457,6 +457,8 @@ golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BG golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 h1:ld7aEMNHoBnnDAX15v1T6z31v8HwR2A9FYOuAhWqkwc= golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58 h1:Mj83v+wSRNEar42a/MQgxk9X42TdEmrOl9i+y8WbxLo= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -606,6 +608,8 @@ google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpC google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= diff --git a/integrationTest/integration_test.go b/integrationTest/integration_test.go index 683999645..968d87a2d 100644 --- a/integrationTest/integration_test.go +++ b/integrationTest/integration_test.go @@ -680,9 +680,11 @@ func makeTests(t *testing.T) []*TestGroup { tc("Change a TXT", txt("foo", "changed")), clear(), tc("Create a TXT with spaces", txt("foo", "with spaces")), - tc("Create 1 TXT as array", txtmulti("foo", []string{"simple"})), + tc("Create 1 TXT as array", txtmulti("foo", []string{"simple"})), // Same as non-TXTMulti clear(), tc("Create a 255-byte TXT", txt("foo", strings.Repeat("A", 255))), + clear(), + tc("Create TXT with single-quote", txt("foo", "blah`blah")), ), testgroup("ws TXT", @@ -862,6 +864,42 @@ func makeTests(t *testing.T) []*TestGroup { 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"}), @@ -869,11 +907,9 @@ func makeTests(t *testing.T) []*TestGroup { ), tc("Change TXTMulti", txtmulti("foo1", []string{"dimple"}), - txtmulti("foo2", []string{"fun", "two"}), + txtmulti("foo2", []string{"fun", "t\"wo"}), txtmulti("foo3", []string{"eh", "bzz", "cee"}), ), - tc("3x255-byte TXTMulti", - txtmulti("foo3", []string{strings.Repeat("X", 255), strings.Repeat("Y", 255), strings.Repeat("Z", 255)})), ), testgroup("DS", diff --git a/providers/digitalocean/digitaloceanProvider.go b/providers/digitalocean/digitaloceanProvider.go index 94f795d04..cfd1b7540 100644 --- a/providers/digitalocean/digitaloceanProvider.go +++ b/providers/digitalocean/digitaloceanProvider.go @@ -74,11 +74,9 @@ var features = providers.DocumentationNotes{ providers.DocCreateDomains: providers.Can(), providers.DocOfficiallySupported: providers.Cannot(), providers.CanUseSRV: providers.Can(), - // Digitalocean support CAA records, except - // ";" value with issue/issuewild records: - // https://www.digitalocean.com/docs/networking/dns/how-to/create-caa-records/ - providers.CanUseCAA: providers.Can(), - providers.CanGetZones: 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.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() { @@ -329,7 +327,7 @@ func pauseAndRetry(resp *godo.Response) bool { } // a simple exponential back-off with a 3-minute max. - log.Printf("Pausing due to ratelimit: %v seconds\n", backoff) + log.Printf("Delaying %v due to ratelimit\n", backoff) time.Sleep(backoff) backoff = backoff + (backoff / 2) if backoff > maxBackoff {