From 46680f6524bc6c80951fcac6914b54c03fa363eb Mon Sep 17 00:00:00 2001 From: kolaente Date: Fri, 1 Nov 2019 11:20:34 +0100 Subject: [PATCH] Add support for autodns (#957) --- README.md | 30 ++--- cmd/zz_gen_cmd_dnshelp.go | 24 ++++ docs/content/dns/zz_gen_autodns.md | 66 +++++++++++ providers/dns/autodns/autodns.go | 127 ++++++++++++++++++++ providers/dns/autodns/autodns.toml | 26 ++++ providers/dns/autodns/autodns_test.go | 147 +++++++++++++++++++++++ providers/dns/autodns/client.go | 164 ++++++++++++++++++++++++++ providers/dns/dns_providers.go | 3 + 8 files changed, 572 insertions(+), 15 deletions(-) create mode 100644 docs/content/dns/zz_gen_autodns.md create mode 100644 providers/dns/autodns/autodns.go create mode 100644 providers/dns/autodns/autodns.toml create mode 100644 providers/dns/autodns/autodns_test.go create mode 100644 providers/dns/autodns/client.go diff --git a/README.md b/README.md index 98695acb..704caccf 100644 --- a/README.md +++ b/README.md @@ -44,18 +44,18 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). | | | | | |---------------------------------------------------------------------------------|---------------------------------------------------------------------------------|---------------------------------------------------------------------------------|---------------------------------------------------------------------------------| | [Alibaba Cloud DNS](https://go-acme.github.io/lego/dns/alidns/) | [Amazon Lightsail](https://go-acme.github.io/lego/dns/lightsail/) | [Amazon Route 53](https://go-acme.github.io/lego/dns/route53/) | [Aurora DNS](https://go-acme.github.io/lego/dns/auroradns/) | -| [Azure](https://go-acme.github.io/lego/dns/azure/) | [Bindman](https://go-acme.github.io/lego/dns/bindman/) | [Bluecat](https://go-acme.github.io/lego/dns/bluecat/) | [Cloudflare](https://go-acme.github.io/lego/dns/cloudflare/) | -| [ClouDNS](https://go-acme.github.io/lego/dns/cloudns/) | [CloudXNS](https://go-acme.github.io/lego/dns/cloudxns/) | [ConoHa](https://go-acme.github.io/lego/dns/conoha/) | [Designate DNSaaS for Openstack](https://go-acme.github.io/lego/dns/designate/) | -| [Digital Ocean](https://go-acme.github.io/lego/dns/digitalocean/) | [DNS Made Easy](https://go-acme.github.io/lego/dns/dnsmadeeasy/) | [DNSimple](https://go-acme.github.io/lego/dns/dnsimple/) | [DNSPod](https://go-acme.github.io/lego/dns/dnspod/) | -| [Domain Offensive (do.de)](https://go-acme.github.io/lego/dns/dode/) | [DreamHost](https://go-acme.github.io/lego/dns/dreamhost/) | [Duck DNS](https://go-acme.github.io/lego/dns/duckdns/) | [Dyn](https://go-acme.github.io/lego/dns/dyn/) | -| [EasyDNS](https://go-acme.github.io/lego/dns/easydns/) | [Exoscale](https://go-acme.github.io/lego/dns/exoscale/) | [External program](https://go-acme.github.io/lego/dns/exec/) | [FastDNS](https://go-acme.github.io/lego/dns/fastdns/) | -| [Gandi Live DNS (v5)](https://go-acme.github.io/lego/dns/gandiv5/) | [Gandi](https://go-acme.github.io/lego/dns/gandi/) | [Glesys](https://go-acme.github.io/lego/dns/glesys/) | [Go Daddy](https://go-acme.github.io/lego/dns/godaddy/) | -| [Google Cloud](https://go-acme.github.io/lego/dns/gcloud/) | [Hosting.de](https://go-acme.github.io/lego/dns/hostingde/) | [HTTP request](https://go-acme.github.io/lego/dns/httpreq/) | [Internet Initiative Japan](https://go-acme.github.io/lego/dns/iij/) | -| [INWX](https://go-acme.github.io/lego/dns/inwx/) | [Joker](https://go-acme.github.io/lego/dns/joker/) | [Joohoi's ACME-DNS](https://go-acme.github.io/lego/dns/acme-dns) | [Linode (deprecated)](https://go-acme.github.io/lego/dns/linode/) | -| [Linode (v4)](https://go-acme.github.io/lego/dns/linodev4/) | [Liquid Web](https://go-acme.github.io/lego/dns/liquidweb/) | [Manual](https://go-acme.github.io/lego/dns/manual/) | [MyDNS.jp](https://go-acme.github.io/lego/dns/mydnsjp/) | -| [Name.com](https://go-acme.github.io/lego/dns/namedotcom/) | [Namecheap](https://go-acme.github.io/lego/dns/namecheap/) | [Namesilo](https://go-acme.github.io/lego/dns/namesilo/) | [Netcup](https://go-acme.github.io/lego/dns/netcup/) | -| [NIFCloud](https://go-acme.github.io/lego/dns/nifcloud/) | [NS1](https://go-acme.github.io/lego/dns/ns1/) | [Open Telekom Cloud](https://go-acme.github.io/lego/dns/otc/) | [Oracle Cloud](https://go-acme.github.io/lego/dns/oraclecloud/) | -| [OVH](https://go-acme.github.io/lego/dns/ovh/) | [PowerDNS](https://go-acme.github.io/lego/dns/pdns/) | [Rackspace](https://go-acme.github.io/lego/dns/rackspace/) | [RFC2136](https://go-acme.github.io/lego/dns/rfc2136/) | -| [Sakura Cloud](https://go-acme.github.io/lego/dns/sakuracloud/) | [Selectel](https://go-acme.github.io/lego/dns/selectel/) | [Stackpath](https://go-acme.github.io/lego/dns/stackpath/) | [TransIP](https://go-acme.github.io/lego/dns/transip/) | -| [VegaDNS](https://go-acme.github.io/lego/dns/vegadns/) | [Vscale](https://go-acme.github.io/lego/dns/vscale/) | [Versio](https://go-acme.github.io/lego/dns/versio/) | [Vultr](https://go-acme.github.io/lego/dns/vultr/) | -| [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/) +| [Autodns](https://go-acme.github.io/lego/dns/autodns/) | [Azure](https://go-acme.github.io/lego/dns/azure/) | [Bindman](https://go-acme.github.io/lego/dns/bindman/) | [Bluecat](https://go-acme.github.io/lego/dns/bluecat/) | +| [Cloudflare](https://go-acme.github.io/lego/dns/cloudflare/) | [ClouDNS](https://go-acme.github.io/lego/dns/cloudns/) | [CloudXNS](https://go-acme.github.io/lego/dns/cloudxns/) | [ConoHa](https://go-acme.github.io/lego/dns/conoha/) | +| [Designate DNSaaS for Openstack](https://go-acme.github.io/lego/dns/designate/) | [Digital Ocean](https://go-acme.github.io/lego/dns/digitalocean/) | [DNS Made Easy](https://go-acme.github.io/lego/dns/dnsmadeeasy/) | [DNSimple](https://go-acme.github.io/lego/dns/dnsimple/) | +| [DNSPod](https://go-acme.github.io/lego/dns/dnspod/) | [Domain Offensive (do.de)](https://go-acme.github.io/lego/dns/dode/) | [DreamHost](https://go-acme.github.io/lego/dns/dreamhost/) | [Duck DNS](https://go-acme.github.io/lego/dns/duckdns/) | +| [Dyn](https://go-acme.github.io/lego/dns/dyn/) | [EasyDNS](https://go-acme.github.io/lego/dns/easydns/) | [Exoscale](https://go-acme.github.io/lego/dns/exoscale/) | [External program](https://go-acme.github.io/lego/dns/exec/) | +| [FastDNS](https://go-acme.github.io/lego/dns/fastdns/) | [Gandi Live DNS (v5)](https://go-acme.github.io/lego/dns/gandiv5/) | [Gandi](https://go-acme.github.io/lego/dns/gandi/) | [Glesys](https://go-acme.github.io/lego/dns/glesys/) | +| [Go Daddy](https://go-acme.github.io/lego/dns/godaddy/) | [Google Cloud](https://go-acme.github.io/lego/dns/gcloud/) | [Hosting.de](https://go-acme.github.io/lego/dns/hostingde/) | [HTTP request](https://go-acme.github.io/lego/dns/httpreq/) | +| [Internet Initiative Japan](https://go-acme.github.io/lego/dns/iij/) | [INWX](https://go-acme.github.io/lego/dns/inwx/) | [Joker](https://go-acme.github.io/lego/dns/joker/) | [Joohoi's ACME-DNS](https://go-acme.github.io/lego/dns/acme-dns) | +| [Linode (deprecated)](https://go-acme.github.io/lego/dns/linode/) | [Linode (v4)](https://go-acme.github.io/lego/dns/linodev4/) | [Liquid Web](https://go-acme.github.io/lego/dns/liquidweb/) | [Manual](https://go-acme.github.io/lego/dns/manual/) | +| [MyDNS.jp](https://go-acme.github.io/lego/dns/mydnsjp/) | [Name.com](https://go-acme.github.io/lego/dns/namedotcom/) | [Namecheap](https://go-acme.github.io/lego/dns/namecheap/) | [Namesilo](https://go-acme.github.io/lego/dns/namesilo/) | +| [Netcup](https://go-acme.github.io/lego/dns/netcup/) | [NIFCloud](https://go-acme.github.io/lego/dns/nifcloud/) | [NS1](https://go-acme.github.io/lego/dns/ns1/) | [Open Telekom Cloud](https://go-acme.github.io/lego/dns/otc/) | +| [Oracle Cloud](https://go-acme.github.io/lego/dns/oraclecloud/) | [OVH](https://go-acme.github.io/lego/dns/ovh/) | [PowerDNS](https://go-acme.github.io/lego/dns/pdns/) | [Rackspace](https://go-acme.github.io/lego/dns/rackspace/) | +| [RFC2136](https://go-acme.github.io/lego/dns/rfc2136/) | [Sakura Cloud](https://go-acme.github.io/lego/dns/sakuracloud/) | [Selectel](https://go-acme.github.io/lego/dns/selectel/) | [Stackpath](https://go-acme.github.io/lego/dns/stackpath/) | +| [TransIP](https://go-acme.github.io/lego/dns/transip/) | [VegaDNS](https://go-acme.github.io/lego/dns/vegadns/) | [Vscale](https://go-acme.github.io/lego/dns/vscale/) | [Versio](https://go-acme.github.io/lego/dns/versio/) | +| [Vultr](https://go-acme.github.io/lego/dns/vultr/) | [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/) diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index fa131073..c7df8ca6 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -17,6 +17,7 @@ func allDNSCodes() string { "acme-dns", "alidns", "auroradns", + "autodns", "azure", "bindman", "bluecat", @@ -141,6 +142,29 @@ func displayDNSHelp(name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/auroradns`) + case "autodns": + // generated from: providers/dns/autodns/autodns.toml + ew.writeln(`Configuration for Autodns.`) + ew.writeln(`Code: 'autodns'`) + ew.writeln(`Since: 'v3.1.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "AUTODNS_API_PASSWORD": User Password`) + ew.writeln(` - "AUTODNS_API_USER": Username`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "AUTODNS_CONTEXT": API context (4 for production, 1 for testing. Defaults to 4)`) + ew.writeln(` - "AUTODNS_ENDPOINT": API endpoint URL, defaults to https://api.autodns.com/v1/`) + ew.writeln(` - "AUTODNS_HTTP_TIMEOUT": API request timeout, defaults to 30 seconds`) + ew.writeln(` - "AUTODNS_POLLING_INTERVAL": Time between DNS propagation check`) + ew.writeln(` - "AUTODNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) + ew.writeln(` - "AUTODNS_TTL": The TTL of the TXT record used for the DNS challenge`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/autodns`) + case "azure": // generated from: providers/dns/azure/azure.toml ew.writeln(`Configuration for Azure.`) diff --git a/docs/content/dns/zz_gen_autodns.md b/docs/content/dns/zz_gen_autodns.md new file mode 100644 index 00000000..53af4bed --- /dev/null +++ b/docs/content/dns/zz_gen_autodns.md @@ -0,0 +1,66 @@ +--- +title: "Autodns" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: autodns +--- + + + + + +Since: v3.1.0 + +Configuration for [Autodns](https://www.internetx.com/domains/autodns/). + + + + +- Code: `autodns` + +Here is an example bash command using the Autodns provider: + +```bash +AUTODNS_API_USER=usernam \ +AUTODNS_API_PASSWORD=supersecretpassword \ +lego --dns autodns --domains my.domain.com --email my@email.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `AUTODNS_API_PASSWORD` | User Password | +| `AUTODNS_API_USER` | Username | + +The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. +More information [here](/lego/dns/#configuration-and-credentials). + + +## Additional Configuration + +| Environment Variable Name | Description | +|--------------------------------|-------------| +| `AUTODNS_CONTEXT` | API context (4 for production, 1 for testing. Defaults to 4) | +| `AUTODNS_ENDPOINT` | API endpoint URL, defaults to https://api.autodns.com/v1/ | +| `AUTODNS_HTTP_TIMEOUT` | API request timeout, defaults to 30 seconds | +| `AUTODNS_POLLING_INTERVAL` | Time between DNS propagation check | +| `AUTODNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | +| `AUTODNS_TTL` | The TTL of the TXT record used for the DNS challenge | + +The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. +More information [here](/lego/dns/#configuration-and-credentials). + + + + +## More information + +- [API documentation](https://help.internetx.com/display/APIJSONEN) + + + + diff --git a/providers/dns/autodns/autodns.go b/providers/dns/autodns/autodns.go new file mode 100644 index 00000000..cde23645 --- /dev/null +++ b/providers/dns/autodns/autodns.go @@ -0,0 +1,127 @@ +package autodns + +import ( + "fmt" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v3/challenge/dns01" + "github.com/go-acme/lego/v3/platform/config/env" +) + +const ( + envAPIUser = "AUTODNS_API_USER" + envAPIPassword = "AUTODNS_API_PASSWORD" + envAPIEndpoint = "AUTODNS_ENDPOINT" + envAPIEndpointContext = "AUTODNS_CONTEXT" + envTTL = "AUTODNS_TTL" + envPropagationTimeout = "AUTODNS_PROPAGATION_TIMEOUT" + envPollingInterval = "AUTODNS_POLLING_INTERVAL" + envHTTPTimeout = "AUTODNS_HTTP_TIMEOUT" +) + +const ( + defaultEndpointContext int = 4 + defaultTTL int = 600 +) + +type Config struct { + Endpoint *url.URL + Username string + Password string + Context int + TTL int + PropagationTimeout time.Duration + PollingInterval time.Duration + HTTPClient *http.Client +} + +func NewDefaultConfig() *Config { + endpoint, _ := url.Parse(env.GetOrDefaultString(envAPIEndpoint, defaultEndpoint)) + + return &Config{ + Endpoint: endpoint, + Context: env.GetOrDefaultInt(envAPIEndpointContext, defaultEndpointContext), + TTL: env.GetOrDefaultInt(envTTL, defaultTTL), + PropagationTimeout: env.GetOrDefaultSecond(envPropagationTimeout, 2*time.Minute), + PollingInterval: env.GetOrDefaultSecond(envPollingInterval, 2*time.Second), + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond(envHTTPTimeout, 30*time.Second), + }, + } +} + +type DNSProvider struct { + config *Config +} + +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval +} + +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(envAPIUser, envAPIPassword) + if err != nil { + return nil, fmt.Errorf("autodns: %v", err) + } + + config := NewDefaultConfig() + config.Username = values[envAPIUser] + config.Password = values[envAPIPassword] + + return NewDNSProviderConfig(config) +} + +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, fmt.Errorf("autodns: config is nil") + } + + if config.Username == "" { + return nil, fmt.Errorf("autodns: missing user") + } + + if config.Password == "" { + return nil, fmt.Errorf("autodns: missing password") + } + + return &DNSProvider{config: config}, nil +} + +// Present creates a TXT record to fulfill the dns-01 challenge +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + fqdn, value := dns01.GetRecord(domain, keyAuth) + + records := []*ResourceRecord{{ + Name: fqdn, + TTL: int64(d.config.TTL), + Type: "TXT", + Value: value, + }} + + _, err := d.addTxtRecord(domain, records) + if err != nil { + return fmt.Errorf("autodns: %v", err) + } + + return nil +} + +// CleanUp removes the TXT record previously created +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + fqdn, value := dns01.GetRecord(domain, keyAuth) + + records := []*ResourceRecord{{ + Name: fqdn, + TTL: int64(d.config.TTL), + Type: "TXT", + Value: value, + }} + + if err := d.removeTXTRecord(domain, records); err != nil { + return fmt.Errorf("autodns: %v", err) + } + + return nil +} diff --git a/providers/dns/autodns/autodns.toml b/providers/dns/autodns/autodns.toml new file mode 100644 index 00000000..2e13a8fa --- /dev/null +++ b/providers/dns/autodns/autodns.toml @@ -0,0 +1,26 @@ +Name = "Autodns" +Description = '''''' +URL = "https://www.internetx.com/domains/autodns/" +Code = "autodns" +Since = "v3.1.0" + +Example = ''' +AUTODNS_API_USER=usernam \ +AUTODNS_API_PASSWORD=supersecretpassword \ +lego --dns autodns --domains my.domain.com --email my@email.com run +''' + +[Configuration] + [Configuration.Credentials] + AUTODNS_API_USER = "Username" + AUTODNS_API_PASSWORD = "User Password" + [Configuration.Additional] + AUTODNS_ENDPOINT = "API endpoint URL, defaults to https://api.autodns.com/v1/" + AUTODNS_CONTEXT = "API context (4 for production, 1 for testing. Defaults to 4)" + AUTODNS_TTL = "The TTL of the TXT record used for the DNS challenge" + AUTODNS_POLLING_INTERVAL = "Time between DNS propagation check" + AUTODNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" + AUTODNS_HTTP_TIMEOUT = "API request timeout, defaults to 30 seconds" + +[Links] + API = "https://help.internetx.com/display/APIJSONEN" diff --git a/providers/dns/autodns/autodns_test.go b/providers/dns/autodns/autodns_test.go new file mode 100644 index 00000000..676d4bd2 --- /dev/null +++ b/providers/dns/autodns/autodns_test.go @@ -0,0 +1,147 @@ +package autodns + +import ( + "testing" + + "github.com/go-acme/lego/v3/platform/tester" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var envTest = tester.NewEnvTest(envAPIEndpoint, envAPIUser, envAPIPassword) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + envAPIUser: "123", + envAPIPassword: "456", + }, + }, + { + desc: "missing credentials", + envVars: map[string]string{ + envAPIUser: "", + envAPIPassword: "", + }, + expected: "autodns: some credentials information are missing: AUTODNS_API_USER,AUTODNS_API_PASSWORD", + }, + { + desc: "missing user id", + envVars: map[string]string{ + envAPIUser: "", + envAPIPassword: "456", + }, + expected: "autodns: some credentials information are missing: AUTODNS_API_USER", + }, + { + desc: "missing key", + envVars: map[string]string{ + envAPIUser: "123", + envAPIPassword: "", + }, + expected: "autodns: some credentials information are missing: AUTODNS_API_PASSWORD", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + defer envTest.RestoreEnv() + envTest.ClearEnv() + + envTest.Apply(test.envVars) + + p, err := NewDNSProvider() + + if len(test.expected) == 0 { + require.NoError(t, err) + require.NotNil(t, p) + require.NotNil(t, p.config) + } else { + require.EqualError(t, err, test.expected) + } + }) + } +} + +func TestNewDNSProviderConfig(t *testing.T) { + testCases := []struct { + desc string + username string + password string + expected string + }{ + { + desc: "success", + username: "123", + password: "456", + }, + { + desc: "missing credentials", + username: "", + password: "", + expected: "autodns: missing user", + }, + { + desc: "missing user id", + username: "", + password: "456", + expected: "autodns: missing user", + }, + { + desc: "missing key", + username: "123", + password: "", + expected: "autodns: missing password", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.Username = test.username + config.Password = test.password + + p, err := NewDNSProviderConfig(config) + + if len(test.expected) == 0 { + require.NoError(t, err) + require.NotNil(t, p) + require.NotNil(t, p.config) + } else { + require.EqualError(t, err, test.expected) + } + }) + } +} + +func TestLivePresent(t *testing.T) { + if !envTest.IsLiveTest() { + t.Skip("skipping live test") + } + + envTest.RestoreEnv() + provider, err := NewDNSProvider() + assert.NoError(t, err) + + err = provider.Present(envTest.GetDomain(), "", "123d==") + assert.NoError(t, err) +} + +func TestLiveCleanUp(t *testing.T) { + if !envTest.IsLiveTest() { + t.Skip("skipping live test") + } + + envTest.RestoreEnv() + provider, err := NewDNSProvider() + assert.NoError(t, err) + + err = provider.CleanUp(envTest.GetDomain(), "", "123d==") + assert.NoError(t, err) +} diff --git a/providers/dns/autodns/client.go b/providers/dns/autodns/client.go new file mode 100644 index 00000000..a5b184d7 --- /dev/null +++ b/providers/dns/autodns/client.go @@ -0,0 +1,164 @@ +package autodns + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "path" + "strconv" +) + +const ( + defaultEndpoint = "https://api.autodns.com/v1/" +) + +type ResponseMessage struct { + Text string `json:"text"` + Messages []string `json:"messages"` + Objects []string `json:"objects"` + Code string `json:"code"` + Status string `json:"status"` +} + +type ResponseStatus struct { + Code string `json:"code"` + Text string `json:"text"` + Type string `json:"type"` +} + +type ResponseObject struct { + Type string `json:"type"` + Value string `json:"value"` + Summary int32 `json:"summary"` + Data string +} + +type DataZoneResponse struct { + STID string `json:"stid"` + CTID string `json:"ctid"` + Messages []*ResponseMessage `json:"messages"` + Status *ResponseStatus `json:"status"` + Object interface{} `json:"object"` + Data []*Zone `json:"data"` +} + +// ResourceRecord holds a resource record +type ResourceRecord struct { + Name string `json:"name"` + TTL int64 `json:"ttl"` + Type string `json:"type"` + Value string `json:"value"` + Pref int32 `json:"pref,omitempty"` +} + +// Zone is an autodns zone record with all for us relevant fields +type Zone struct { + Name string `json:"origin"` + ResourceRecords []*ResourceRecord `json:"resourceRecords"` + Action string `json:"action"` + VirtualNameServer string `json:"virtualNameServer"` +} + +type ZoneStream struct { + Adds []*ResourceRecord `json:"adds"` + Removes []*ResourceRecord `json:"rems"` +} + +func (d *DNSProvider) addTxtRecord(domain string, records []*ResourceRecord) (*Zone, error) { + zoneStream := &ZoneStream{Adds: records} + + return d.makeZoneUpdateRequest(zoneStream, domain) +} + +func (d *DNSProvider) removeTXTRecord(domain string, records []*ResourceRecord) error { + zoneStream := &ZoneStream{Adds: records} + + _, err := d.makeZoneUpdateRequest(zoneStream, domain) + return err +} + +func (d *DNSProvider) makeZoneUpdateRequest(zoneStream *ZoneStream, domain string) (*Zone, error) { + reqBody := &bytes.Buffer{} + if err := json.NewEncoder(reqBody).Encode(zoneStream); err != nil { + return nil, err + } + + req, err := d.makeRequest(http.MethodPost, path.Join("zone", domain, "_stream"), reqBody) + if err != nil { + return nil, err + } + + var resp *Zone + if err := d.sendRequest(req, &resp); err != nil { + return nil, err + } + return resp, nil +} + +func (d *DNSProvider) makeRequest(method, resource string, body io.Reader) (*http.Request, error) { + uri, err := d.config.Endpoint.Parse(resource) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(method, uri.String(), body) + if err != nil { + return nil, err + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Domainrobot-Context", strconv.Itoa(d.config.Context)) + req.SetBasicAuth(d.config.Username, d.config.Password) + + return req, nil +} + +func (d *DNSProvider) sendRequest(req *http.Request, result interface{}) error { + resp, err := d.config.HTTPClient.Do(req) + if err != nil { + return err + } + + if err = checkResponse(resp); err != nil { + return err + } + + defer func() { _ = resp.Body.Close() }() + + if result == nil { + return nil + } + + raw, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + + err = json.Unmarshal(raw, result) + if err != nil { + return fmt.Errorf("unmarshaling %T error [status code=%d]: %v: %s", result, resp.StatusCode, err, string(raw)) + } + return err +} + +func checkResponse(resp *http.Response) error { + if resp.StatusCode < http.StatusBadRequest { + return nil + } + + if resp.Body == nil { + return fmt.Errorf("response body is nil, status code=%d", resp.StatusCode) + } + + defer func() { _ = resp.Body.Close() }() + + raw, err := ioutil.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("unable to read body: status code=%d, error=%v", resp.StatusCode, err) + } + + return fmt.Errorf("status code=%d: %s", resp.StatusCode, string(raw)) +} diff --git a/providers/dns/dns_providers.go b/providers/dns/dns_providers.go index fe5f55cc..6928e5ec 100644 --- a/providers/dns/dns_providers.go +++ b/providers/dns/dns_providers.go @@ -8,6 +8,7 @@ import ( "github.com/go-acme/lego/v3/providers/dns/acmedns" "github.com/go-acme/lego/v3/providers/dns/alidns" "github.com/go-acme/lego/v3/providers/dns/auroradns" + "github.com/go-acme/lego/v3/providers/dns/autodns" "github.com/go-acme/lego/v3/providers/dns/azure" "github.com/go-acme/lego/v3/providers/dns/bindman" "github.com/go-acme/lego/v3/providers/dns/bluecat" @@ -78,6 +79,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return azure.NewDNSProvider() case "auroradns": return auroradns.NewDNSProvider() + case "autodns": + return autodns.NewDNSProvider() case "bindman": return bindman.NewDNSProvider() case "bluecat":