1
0
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:
evan
2024-01-02 15:40:40 -06:00
committed by GitHub
parent 94cdbc0290
commit afd0d76e7b
9 changed files with 244 additions and 1 deletions

View File

@ -35,7 +35,7 @@ changelog:
regexp: "(?i)^.*(major|new provider|feature)[(\\w)]*:+.*$"
order: 1
- 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
- title: 'Documentation:'
regexp: "(?i)^.*(docs)[(\\w)]*:+.*$"

1
OWNERS
View File

@ -13,6 +13,7 @@ providers/dnsimple @onlyhavecans
providers/dnsmadeeasy @vojtad
providers/doh @mikenz
providers/domainnameshop @SimenBai
providers/dynadot @e-im
providers/easyname @tresni
providers/exoscale @pierre-emmanuelJ
providers/gandiv5 @TomOnTime

View File

@ -64,6 +64,7 @@ Currently supported Domain Registrars:
- AWS Route 53
- CSC Global
- DNSOVERHTTPS
- Dynadot
- easyname
- Gandi
- HEXONET

View File

@ -113,6 +113,7 @@
* [DNSimple](providers/dnsimple.md)
* [DNS-over-HTTPS](providers/dnsoverhttps.md)
* [DOMAINNAMESHOP](providers/domainnameshop.md)
* [Dynadot](providers/dynadot.md)
* [easyname](providers/easyname.md)
* [Exoscale](providers/exoscale.md)
* [Gandi_v5](providers/gandi_v5.md)

View File

@ -29,6 +29,7 @@ If a feature is definitively not supported for whatever reason, we would also li
| [`DNSMADEEASY`](providers/dnsmadeeasy.md) | ❌ | ✅ | ❌ | ✅ | ✅ | ❔ | ❌ | ❔ | ✅ | ❔ | ✅ | ❌ | ❌ | ❌ | ❔ | ✅ | ✅ | ✅ | ✅ |
| [`DNSOVERHTTPS`](providers/dnsoverhttps.md) | ❌ | ❌ | ✅ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❌ | ✅ | ❔ |
| [`DOMAINNAMESHOP`](providers/domainnameshop.md) | ❌ | ✅ | ❌ | ❔ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❔ | ❔ | ❔ | ❔ | ❔ | ✅ | ❔ |
| [`DYNADOT`](providers/dynadot.md) | ❌ | ❌ | ✅ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❌ | ✅ | ❔ |
| [`EASYNAME`](providers/easyname.md) | ❌ | ❌ | ✅ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❌ | ✅ | ❔ |
| [`EXOSCALE`](providers/exoscale.md) | ❌ | ✅ | ❌ | ✅ | ✅ | ❔ | ❌ | ❔ | ✅ | ❔ | ✅ | ❔ | ❌ | ❔ | ❔ | ❌ | ❌ | ✅ | ❔ |
| [`GANDI_V5`](providers/gandi_v5.md) | ❌ | ✅ | ✅ | ✅ | ✅ | ❔ | ❌ | ❔ | ✅ | ❔ | ✅ | ✅ | ✅ | ❌ | ❔ | ❔ | ❌ | ❌ | ✅ |

View 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.

View File

@ -18,6 +18,7 @@ import (
_ "github.com/StackExchange/dnscontrol/v4/providers/dnsmadeeasy"
_ "github.com/StackExchange/dnscontrol/v4/providers/doh"
_ "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/exoscale"
_ "github.com/StackExchange/dnscontrol/v4/providers/gandiv5"

135
providers/dynadot/api.go Normal file
View 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)
}

View 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
}