mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
NEW REGISTRAR: Dynadot (DYNADOT) (#2753)
Co-authored-by: Tom Limoncelli <tlimoncelli@stackoverflow.com>
This commit is contained in:
@ -35,7 +35,7 @@ changelog:
|
|||||||
regexp: "(?i)^.*(major|new provider|feature)[(\\w)]*:+.*$"
|
regexp: "(?i)^.*(major|new provider|feature)[(\\w)]*:+.*$"
|
||||||
order: 1
|
order: 1
|
||||||
- title: 'Provider-specific changes:'
|
- title: 'Provider-specific changes:'
|
||||||
regexp: "(?i)((akamaiedge|autodns|axfrd|azure|azure_private_dns|bind|cloudflare|cloudflareapi_old|cloudns|cscglobal|desec|digitalocean|dnsimple|dnsmadeeasy|doh|domainnameshop|easyname|exoscale|gandi|gcloud|gcore|hedns|hetzner|hexonet|hostingde|inwx|linode|loopia|luadns|msdns|mythicbeasts|namecheap|namedotcom|netcup|netlify|ns1|opensrs|oracle|ovh|packetframe|porkbun|powerdns|route53|rwth|softlayer|transip|vultr).*:)+.*"
|
regexp: "(?i)((akamaiedge|autodns|axfrd|azure|azure_private_dns|bind|cloudflare|cloudflareapi_old|cloudns|cscglobal|desec|digitalocean|dnsimple|dnsmadeeasy|doh|domainnameshop|dynadot|easyname|exoscale|gandi|gcloud|gcore|hedns|hetzner|hexonet|hostingde|inwx|linode|loopia|luadns|msdns|mythicbeasts|namecheap|namedotcom|netcup|netlify|ns1|opensrs|oracle|ovh|packetframe|porkbun|powerdns|route53|rwth|softlayer|transip|vultr).*:)+.*"
|
||||||
order: 2
|
order: 2
|
||||||
- title: 'Documentation:'
|
- title: 'Documentation:'
|
||||||
regexp: "(?i)^.*(docs)[(\\w)]*:+.*$"
|
regexp: "(?i)^.*(docs)[(\\w)]*:+.*$"
|
||||||
|
1
OWNERS
1
OWNERS
@ -13,6 +13,7 @@ providers/dnsimple @onlyhavecans
|
|||||||
providers/dnsmadeeasy @vojtad
|
providers/dnsmadeeasy @vojtad
|
||||||
providers/doh @mikenz
|
providers/doh @mikenz
|
||||||
providers/domainnameshop @SimenBai
|
providers/domainnameshop @SimenBai
|
||||||
|
providers/dynadot @e-im
|
||||||
providers/easyname @tresni
|
providers/easyname @tresni
|
||||||
providers/exoscale @pierre-emmanuelJ
|
providers/exoscale @pierre-emmanuelJ
|
||||||
providers/gandiv5 @TomOnTime
|
providers/gandiv5 @TomOnTime
|
||||||
|
@ -64,6 +64,7 @@ Currently supported Domain Registrars:
|
|||||||
- AWS Route 53
|
- AWS Route 53
|
||||||
- CSC Global
|
- CSC Global
|
||||||
- DNSOVERHTTPS
|
- DNSOVERHTTPS
|
||||||
|
- Dynadot
|
||||||
- easyname
|
- easyname
|
||||||
- Gandi
|
- Gandi
|
||||||
- HEXONET
|
- HEXONET
|
||||||
|
@ -113,6 +113,7 @@
|
|||||||
* [DNSimple](providers/dnsimple.md)
|
* [DNSimple](providers/dnsimple.md)
|
||||||
* [DNS-over-HTTPS](providers/dnsoverhttps.md)
|
* [DNS-over-HTTPS](providers/dnsoverhttps.md)
|
||||||
* [DOMAINNAMESHOP](providers/domainnameshop.md)
|
* [DOMAINNAMESHOP](providers/domainnameshop.md)
|
||||||
|
* [Dynadot](providers/dynadot.md)
|
||||||
* [easyname](providers/easyname.md)
|
* [easyname](providers/easyname.md)
|
||||||
* [Exoscale](providers/exoscale.md)
|
* [Exoscale](providers/exoscale.md)
|
||||||
* [Gandi_v5](providers/gandi_v5.md)
|
* [Gandi_v5](providers/gandi_v5.md)
|
||||||
|
@ -29,6 +29,7 @@ If a feature is definitively not supported for whatever reason, we would also li
|
|||||||
| [`DNSMADEEASY`](providers/dnsmadeeasy.md) | ❌ | ✅ | ❌ | ✅ | ✅ | ❔ | ❌ | ❔ | ✅ | ❔ | ✅ | ❌ | ❌ | ❌ | ❔ | ✅ | ✅ | ✅ | ✅ |
|
| [`DNSMADEEASY`](providers/dnsmadeeasy.md) | ❌ | ✅ | ❌ | ✅ | ✅ | ❔ | ❌ | ❔ | ✅ | ❔ | ✅ | ❌ | ❌ | ❌ | ❔ | ✅ | ✅ | ✅ | ✅ |
|
||||||
| [`DNSOVERHTTPS`](providers/dnsoverhttps.md) | ❌ | ❌ | ✅ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❌ | ✅ | ❔ |
|
| [`DNSOVERHTTPS`](providers/dnsoverhttps.md) | ❌ | ❌ | ✅ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❌ | ✅ | ❔ |
|
||||||
| [`DOMAINNAMESHOP`](providers/domainnameshop.md) | ❌ | ✅ | ❌ | ❔ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❔ | ❔ | ❔ | ❔ | ❔ | ✅ | ❔ |
|
| [`DOMAINNAMESHOP`](providers/domainnameshop.md) | ❌ | ✅ | ❌ | ❔ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❔ | ❔ | ❔ | ❔ | ❔ | ✅ | ❔ |
|
||||||
|
| [`DYNADOT`](providers/dynadot.md) | ❌ | ❌ | ✅ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❌ | ✅ | ❔ |
|
||||||
| [`EASYNAME`](providers/easyname.md) | ❌ | ❌ | ✅ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❌ | ✅ | ❔ |
|
| [`EASYNAME`](providers/easyname.md) | ❌ | ❌ | ✅ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❌ | ✅ | ❔ |
|
||||||
| [`EXOSCALE`](providers/exoscale.md) | ❌ | ✅ | ❌ | ✅ | ✅ | ❔ | ❌ | ❔ | ✅ | ❔ | ✅ | ❔ | ❌ | ❔ | ❔ | ❌ | ❌ | ✅ | ❔ |
|
| [`EXOSCALE`](providers/exoscale.md) | ❌ | ✅ | ❌ | ✅ | ✅ | ❔ | ❌ | ❔ | ✅ | ❔ | ✅ | ❔ | ❌ | ❔ | ❔ | ❌ | ❌ | ✅ | ❔ |
|
||||||
| [`GANDI_V5`](providers/gandi_v5.md) | ❌ | ✅ | ✅ | ✅ | ✅ | ❔ | ❌ | ❔ | ✅ | ❔ | ✅ | ✅ | ✅ | ❌ | ❔ | ❔ | ❌ | ❌ | ✅ |
|
| [`GANDI_V5`](providers/gandi_v5.md) | ❌ | ✅ | ✅ | ✅ | ✅ | ❔ | ❌ | ❔ | ✅ | ❔ | ✅ | ✅ | ✅ | ❌ | ❔ | ❔ | ❌ | ❌ | ✅ |
|
||||||
|
41
documentation/providers/dynadot.md
Normal file
41
documentation/providers/dynadot.md
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
DNSControl's Dynadot provider supports being a Registrar. Support for being a DNS Provider is not included, but could be added in the future.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
To use this provider, add an entry to `creds.json` with `TYPE` set to `DYNADOT`
|
||||||
|
along with `key` from the [Dynadot API](https://www.dynadot.com/account/domain/setting/api.html).
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
{% code title="creds.json" %}
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"easyname": {
|
||||||
|
"TYPE": "DYNADOT",
|
||||||
|
"key": "API Key",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
{% endcode %}
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
This provider does not recognize any special metadata fields unique to Dynadot.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
An example configuration:
|
||||||
|
|
||||||
|
{% code title="dnsconfig.js" %}
|
||||||
|
```javascript
|
||||||
|
var REG_DYNADOT = NewRegistrar("dynadot");
|
||||||
|
|
||||||
|
DOMAIN_ELSEWHERE("example.com", REG_DYNADOT, [
|
||||||
|
"ns1.example.net.",
|
||||||
|
"ns2.example.net.",
|
||||||
|
"ns3.example.net.",
|
||||||
|
]);
|
||||||
|
```
|
||||||
|
{% endcode %}
|
||||||
|
|
||||||
|
## Activation
|
||||||
|
|
||||||
|
You must [enable the Dynadot API](https://www.dynadot.com/account/domain/setting/api.html) for your account and whitelist the IP address of the machine that will run DNSControl.
|
@ -18,6 +18,7 @@ import (
|
|||||||
_ "github.com/StackExchange/dnscontrol/v4/providers/dnsmadeeasy"
|
_ "github.com/StackExchange/dnscontrol/v4/providers/dnsmadeeasy"
|
||||||
_ "github.com/StackExchange/dnscontrol/v4/providers/doh"
|
_ "github.com/StackExchange/dnscontrol/v4/providers/doh"
|
||||||
_ "github.com/StackExchange/dnscontrol/v4/providers/domainnameshop"
|
_ "github.com/StackExchange/dnscontrol/v4/providers/domainnameshop"
|
||||||
|
_ "github.com/StackExchange/dnscontrol/v4/providers/dynadot"
|
||||||
_ "github.com/StackExchange/dnscontrol/v4/providers/easyname"
|
_ "github.com/StackExchange/dnscontrol/v4/providers/easyname"
|
||||||
_ "github.com/StackExchange/dnscontrol/v4/providers/exoscale"
|
_ "github.com/StackExchange/dnscontrol/v4/providers/exoscale"
|
||||||
_ "github.com/StackExchange/dnscontrol/v4/providers/gandiv5"
|
_ "github.com/StackExchange/dnscontrol/v4/providers/gandiv5"
|
||||||
|
135
providers/dynadot/api.go
Normal file
135
providers/dynadot/api.go
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
package dynadot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// API layer for Dynadot
|
||||||
|
|
||||||
|
type dynadotProvider struct {
|
||||||
|
key string
|
||||||
|
}
|
||||||
|
|
||||||
|
type requestParams map[string]string
|
||||||
|
|
||||||
|
type header struct {
|
||||||
|
SuccessCode int `xml:"SuccessCode"`
|
||||||
|
Status string `xml:"Status"`
|
||||||
|
Error string `xml:"Error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type addNsResponse struct {
|
||||||
|
XMLName xml.Name `xml:"AddNsResponse"`
|
||||||
|
AddNsHeader header `xml:"AddNsHeader"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type setNsResponse struct {
|
||||||
|
XMLName xml.Name `xml:"SetNsResponse"`
|
||||||
|
SetNsHeader header `xml:"SetNsHeader"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type getNsResponse struct {
|
||||||
|
XMLName xml.Name `xml:"GetNsResponse"`
|
||||||
|
NsContent nsContent `xml:"NsContent"`
|
||||||
|
GetNsHeader header `xml:"GetNsHeader"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type nsContent struct {
|
||||||
|
Host []string `xml:"Host"`
|
||||||
|
NsName string `xml:"NsName"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *dynadotProvider) getNameservers(domain string) ([]string, error) {
|
||||||
|
var bodyString, err = c.get("get_ns", requestParams{"domain": domain})
|
||||||
|
if err != nil {
|
||||||
|
return []string{}, fmt.Errorf("failed NS list (Dynadot): %s", err)
|
||||||
|
}
|
||||||
|
var ns getNsResponse
|
||||||
|
xml.Unmarshal(bodyString, &ns)
|
||||||
|
|
||||||
|
if ns.GetNsHeader.SuccessCode != 0 {
|
||||||
|
return []string{}, fmt.Errorf("failed NS list (Dynadot): %s", ns.GetNsHeader.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
hosts := []string{}
|
||||||
|
hosts = append(hosts, ns.NsContent.Host...)
|
||||||
|
return hosts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *dynadotProvider) updateNameservers(ns []string, domain string) error {
|
||||||
|
if len(ns) > 13 {
|
||||||
|
return fmt.Errorf("failed NS update (Dynadot): only up to 13 nameservers are supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nameservers must first be added to the Dynadot account
|
||||||
|
for _, host := range ns {
|
||||||
|
b, err := c.get("add_ns", requestParams{"host": host})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed NS add (Dynadot): %s", err)
|
||||||
|
}
|
||||||
|
var resp addNsResponse
|
||||||
|
err = xml.Unmarshal(b, &resp)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed NS add (Dynadot): %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.AddNsHeader.SuccessCode != 0 {
|
||||||
|
// No apparent way to get all existing entries on an account, so filter
|
||||||
|
if strings.Contains(resp.AddNsHeader.Error, "already exists") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return fmt.Errorf("failed NS add (Dynadot): %s", resp.AddNsHeader.Error)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rec := requestParams{}
|
||||||
|
rec["domain"] = domain
|
||||||
|
// supported prams: ns0 - ns12
|
||||||
|
for i, h := range ns {
|
||||||
|
rec[fmt.Sprintf("%s%d", "ns", i)] = h
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := c.get("set_ns", rec)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed NS set (Dynadot): %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp setNsResponse
|
||||||
|
err = xml.Unmarshal(b, &resp)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed NS add (Dynadot): %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.SetNsHeader.SuccessCode != 0 {
|
||||||
|
return fmt.Errorf("failed NS add (Dynadot): %s", resp.SetNsHeader.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *dynadotProvider) get(command string, params requestParams) ([]byte, error) {
|
||||||
|
client := &http.Client{}
|
||||||
|
req, _ := http.NewRequest("GET", "https://api.dynadot.com/api3.xml", nil)
|
||||||
|
q := req.URL.Query()
|
||||||
|
|
||||||
|
q.Add("key", c.key)
|
||||||
|
q.Add("command", command)
|
||||||
|
|
||||||
|
for pName, pValue := range params {
|
||||||
|
q.Add(pName, pValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.URL.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return io.ReadAll(resp.Body)
|
||||||
|
}
|
62
providers/dynadot/dynadotProvider.go
Normal file
62
providers/dynadot/dynadotProvider.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package dynadot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/StackExchange/dnscontrol/v4/models"
|
||||||
|
"github.com/StackExchange/dnscontrol/v4/providers"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Dynadot Registrator:
|
||||||
|
|
||||||
|
Info required in `creds.json`:
|
||||||
|
- key API Key
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
providers.RegisterRegistrarType("DYNADOT", newDynadot)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDynadot(m map[string]string) (providers.Registrar, error) {
|
||||||
|
d := &dynadotProvider{}
|
||||||
|
|
||||||
|
d.key = m["key"]
|
||||||
|
if d.key == "" {
|
||||||
|
return nil, fmt.Errorf("missing Dynadot key")
|
||||||
|
}
|
||||||
|
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *dynadotProvider) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||||
|
nss, err := c.getNameservers(dc.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
foundNameservers := strings.Join(nss, ",")
|
||||||
|
|
||||||
|
expected := []string{}
|
||||||
|
for _, ns := range dc.Nameservers {
|
||||||
|
name := strings.TrimRight(ns.Name, ".")
|
||||||
|
expected = append(expected, name)
|
||||||
|
}
|
||||||
|
sort.Strings(expected)
|
||||||
|
expectedNameservers := strings.Join(expected, ",")
|
||||||
|
|
||||||
|
if foundNameservers != expectedNameservers {
|
||||||
|
return []*models.Correction{
|
||||||
|
{
|
||||||
|
Msg: fmt.Sprintf("Update nameservers (%s) -> (%s)", foundNameservers, expectedNameservers),
|
||||||
|
F: func() error {
|
||||||
|
return c.updateNameservers(expected, dc.Name)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
Reference in New Issue
Block a user