mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
Initial support for easyname as registrar (#1277)
* Initial support for easyname as registrar * DRY Moving a bunch of the HTTP stuff into a single function for cleanliness and ease of future maintianence. * Seperate API and Provider logic * Updating error messages and sorting found nameservers * Adding provider info to OWNERS and README This also moves a few of the not-actually-DNS Providers to their own section. * Update README.md Co-authored-by: Tom Limoncelli <tlimoncelli@stackoverflow.com>
This commit is contained in:
1
OWNERS
1
OWNERS
@ -10,6 +10,7 @@ providers/doh @mikenz
|
|||||||
providers/digitalocean @Deraen
|
providers/digitalocean @Deraen
|
||||||
providers/dnsimple @aeden
|
providers/dnsimple @aeden
|
||||||
providers/dnsmadeeasy @vojtad
|
providers/dnsmadeeasy @vojtad
|
||||||
|
providers/easyname @tresni
|
||||||
providers/gandi_v5 @TomOnTime
|
providers/gandi_v5 @TomOnTime
|
||||||
# providers/gcloud
|
# providers/gcloud
|
||||||
providers/hedns @rblenkinsopp
|
providers/hedns @rblenkinsopp
|
||||||
|
21
README.md
21
README.md
@ -23,7 +23,7 @@ Currently supported DNS providers:
|
|||||||
- BIND
|
- BIND
|
||||||
- ClouDNS
|
- ClouDNS
|
||||||
- Cloudflare
|
- Cloudflare
|
||||||
- DNSOVERHTTPS
|
- deSEC
|
||||||
- DNS Made Easy
|
- DNS Made Easy
|
||||||
- DNSimple
|
- DNSimple
|
||||||
- DigitalOcean
|
- DigitalOcean
|
||||||
@ -35,7 +35,6 @@ Currently supported DNS providers:
|
|||||||
- hosting.de
|
- hosting.de
|
||||||
- Hurricane Electric DNS
|
- Hurricane Electric DNS
|
||||||
- INWX
|
- INWX
|
||||||
- Internet.bs
|
|
||||||
- Linode
|
- Linode
|
||||||
- Microsoft Windows Server DNS Server
|
- Microsoft Windows Server DNS Server
|
||||||
- NS1
|
- NS1
|
||||||
@ -44,13 +43,27 @@ Currently supported DNS providers:
|
|||||||
- Netcup
|
- Netcup
|
||||||
- OVH
|
- OVH
|
||||||
- OctoDNS
|
- OctoDNS
|
||||||
- OpenSRS
|
|
||||||
- Oracle Cloud
|
- Oracle Cloud
|
||||||
- PowerDNS
|
- PowerDNS
|
||||||
- SoftLayer
|
- SoftLayer
|
||||||
- TransIP
|
- TransIP
|
||||||
- Vultr
|
- Vultr
|
||||||
- deSEC
|
|
||||||
|
Currently supported Domain Registrars:
|
||||||
|
- AWS Route 53
|
||||||
|
- CSC Global
|
||||||
|
- DNSOVERHTTPS
|
||||||
|
- easyname
|
||||||
|
- Gandi
|
||||||
|
- HEXONET
|
||||||
|
- hosting.de
|
||||||
|
- Internet.bs
|
||||||
|
- INWX
|
||||||
|
- Name.com
|
||||||
|
- Namecheap
|
||||||
|
- OVH
|
||||||
|
- OpenSRS
|
||||||
|
|
||||||
|
|
||||||
At Stack Overflow, we use this system to manage hundreds of domains
|
At Stack Overflow, we use this system to manage hundreds of domains
|
||||||
and subdomains across multiple registrars and DNS providers.
|
and subdomains across multiple registrars and DNS providers.
|
||||||
|
43
docs/_providers/easyname.md
Normal file
43
docs/_providers/easyname.md
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
---
|
||||||
|
name: easyname
|
||||||
|
title: easyname Provider
|
||||||
|
layout: default
|
||||||
|
jsId: EASYNAME
|
||||||
|
---
|
||||||
|
# easyname Provider
|
||||||
|
|
||||||
|
DNSControl's easyname provider supports being a Registrar. Support for being a DNS Provider is not included, but could be added in the future.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
In your credentials file, you must provide your [API-Access](https://my.easyname.com/en/account/api) information
|
||||||
|
|
||||||
|
{% highlight json %}
|
||||||
|
{
|
||||||
|
"easyname": {
|
||||||
|
"userid": 12345,
|
||||||
|
"email": "example@example.com",
|
||||||
|
"apikey": "API Key",
|
||||||
|
"authsalt": "API Authentication Salt",
|
||||||
|
"signsalt": "API Signing Salt"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{% endhighlight %}
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
This provider does not recognize any special metadata fields unique to easyname.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
Example Javascript:
|
||||||
|
|
||||||
|
{% highlight js %}
|
||||||
|
var REG_EASYNAME = NewRegistrar('easyname', 'EASYNAME');
|
||||||
|
|
||||||
|
D("example.com", REG_EASYNAME,
|
||||||
|
NAMESERVER("ns1.example.com."),
|
||||||
|
NAMESERVER("ns2.example.com."),
|
||||||
|
);
|
||||||
|
{% endhighlight %}
|
||||||
|
|
||||||
|
## Activation
|
||||||
|
|
||||||
|
You must enable API-Access for your account.
|
@ -80,6 +80,7 @@ Maintainers of contributed providers:
|
|||||||
* `DNSOVERHTTPS` @mikenz
|
* `DNSOVERHTTPS` @mikenz
|
||||||
* `DNSIMPLE` @aeden
|
* `DNSIMPLE` @aeden
|
||||||
* `DNSMADEEASY` @vojtad
|
* `DNSMADEEASY` @vojtad
|
||||||
|
* `EASYNAME` @tresni
|
||||||
* `EXOSCALE` @pierre-emmanuelJ
|
* `EXOSCALE` @pierre-emmanuelJ
|
||||||
* `GANDI_V5` @TomOnTime
|
* `GANDI_V5` @TomOnTime
|
||||||
* `HEDNS` @rblenkinsopp
|
* `HEDNS` @rblenkinsopp
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
_ "github.com/StackExchange/dnscontrol/v3/providers/dnsimple"
|
_ "github.com/StackExchange/dnscontrol/v3/providers/dnsimple"
|
||||||
_ "github.com/StackExchange/dnscontrol/v3/providers/dnsmadeeasy"
|
_ "github.com/StackExchange/dnscontrol/v3/providers/dnsmadeeasy"
|
||||||
_ "github.com/StackExchange/dnscontrol/v3/providers/doh"
|
_ "github.com/StackExchange/dnscontrol/v3/providers/doh"
|
||||||
|
_ "github.com/StackExchange/dnscontrol/v3/providers/easyname"
|
||||||
_ "github.com/StackExchange/dnscontrol/v3/providers/exoscale"
|
_ "github.com/StackExchange/dnscontrol/v3/providers/exoscale"
|
||||||
_ "github.com/StackExchange/dnscontrol/v3/providers/gandiv5"
|
_ "github.com/StackExchange/dnscontrol/v3/providers/gandiv5"
|
||||||
_ "github.com/StackExchange/dnscontrol/v3/providers/gcloud"
|
_ "github.com/StackExchange/dnscontrol/v3/providers/gcloud"
|
||||||
|
140
providers/easyname/api.go
Normal file
140
providers/easyname/api.go
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
package easyname
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type easynameResponse interface {
|
||||||
|
GetStatus() easynameResponseStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
type easynameResponseStatus struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Code int64 `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type easynameResponseData struct {
|
||||||
|
easynameResponseStatus
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
Signature string `json:"signature"`
|
||||||
|
Status *easynameResponseStatus `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c easynameResponseData) GetStatus() easynameResponseStatus {
|
||||||
|
return *c.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
type easynameDomainList struct {
|
||||||
|
easynameResponseData
|
||||||
|
Domains []easynameDomain `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type easynameDomain struct {
|
||||||
|
Id int `json:"id"`
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
NameServer1 string `json:"nameserver1"`
|
||||||
|
NameServer2 string `json:"nameserver2"`
|
||||||
|
NameServer3 string `json:"nameserver3"`
|
||||||
|
NameServer4 string `json:"nameserver4"`
|
||||||
|
NameServer5 string `json:"nameserver5"`
|
||||||
|
NameServer6 string `json:"nameserver6"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type easynameNameserveChange struct {
|
||||||
|
easynameResponseData
|
||||||
|
Nameservers map[string]string `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashEncodeString(s string) string {
|
||||||
|
hash := fmt.Sprintf("%x", md5.Sum([]byte(s)))
|
||||||
|
return base64.StdEncoding.EncodeToString([]byte(hash))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *easynameProvider) request(method, uri string, body *bytes.Buffer, result easynameResponse) error {
|
||||||
|
httpClient := http.Client{}
|
||||||
|
req, err := http.NewRequest(method, uri, body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.Header.Set("X-User-ApiKey", c.apikey)
|
||||||
|
req.Header.Set("X-User-Authentication", c.apiauth)
|
||||||
|
resp, err := httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyString, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
json.Unmarshal(bodyString, &result)
|
||||||
|
|
||||||
|
status := result.GetStatus()
|
||||||
|
if status.Type != "success" && status.Type != "pending" {
|
||||||
|
return fmt.Errorf("easyname error (%d): %s", status.Code, status.Message)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *easynameProvider) updateNameservers(nss []string, domain int) error {
|
||||||
|
var signature string
|
||||||
|
enc := easynameNameserveChange{Nameservers: map[string]string{}}
|
||||||
|
for i, ns := range nss {
|
||||||
|
enc.Nameservers[fmt.Sprintf("nameserver%d", i+1)] = ns
|
||||||
|
signature += ns
|
||||||
|
}
|
||||||
|
|
||||||
|
t := time.Now().Unix()
|
||||||
|
enc.Timestamp = t
|
||||||
|
signature = fmt.Sprintf("%s%d", signature, t)
|
||||||
|
|
||||||
|
insert := len(signature) / 2
|
||||||
|
if len(signature)%2 == 1 {
|
||||||
|
insert++
|
||||||
|
}
|
||||||
|
signingkey := signature[:insert] + c.signSalt + signature[insert:]
|
||||||
|
enc.Signature = hashEncodeString(signingkey)
|
||||||
|
|
||||||
|
body, err := json.Marshal(enc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
er := easynameResponseData{}
|
||||||
|
if err = c.request("POST", fmt.Sprintf("https://api.easyname.com/domain/%d/nameserverchange", domain), bytes.NewBuffer(body), &er); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *easynameProvider) getDomain(domain string) (easynameDomain, error) {
|
||||||
|
if c.domains == nil {
|
||||||
|
c.fetchDomainList()
|
||||||
|
}
|
||||||
|
|
||||||
|
d, ok := c.domains[domain]
|
||||||
|
if !ok {
|
||||||
|
return easynameDomain{}, fmt.Errorf("the domain %s was not found in the easyname account", domain)
|
||||||
|
}
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *easynameProvider) fetchDomainList() error {
|
||||||
|
c.domains = map[string]easynameDomain{}
|
||||||
|
domains := easynameDomainList{}
|
||||||
|
if err := c.request("GET", "https://api.easyname.com/domain", &bytes.Buffer{}, &domains); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, domain := range domains.Domains {
|
||||||
|
c.domains[domain.Domain] = domain
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
71
providers/easyname/provider.go
Normal file
71
providers/easyname/provider.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package easyname
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/providers"
|
||||||
|
)
|
||||||
|
|
||||||
|
type easynameProvider struct {
|
||||||
|
apikey string
|
||||||
|
apiauth string
|
||||||
|
signSalt string
|
||||||
|
domains map[string]easynameDomain
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
providers.RegisterRegistrarType("EASYNAME", newEasyname)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEasyname(m map[string]string) (providers.Registrar, error) {
|
||||||
|
api := &easynameProvider{}
|
||||||
|
|
||||||
|
if m["email"] == "" || m["userid"] == "" || m["apikey"] == "" || m["authsalt"] == "" || m["signsalt"] == "" {
|
||||||
|
return nil, fmt.Errorf("missing easyname email, userid, apikey, authsalt and/or signsalt")
|
||||||
|
}
|
||||||
|
|
||||||
|
api.apikey, api.signSalt = m["apikey"], m["signsalt"]
|
||||||
|
composed := fmt.Sprintf(m["authsalt"], m["userid"], m["email"])
|
||||||
|
api.apiauth = hashEncodeString(composed)
|
||||||
|
|
||||||
|
return api, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRegistrarCorrections gathers corrections that would being n to match dc.
|
||||||
|
func (c *easynameProvider) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||||
|
domain, err := c.getDomain(dc.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nss := []string{}
|
||||||
|
for _, ns := range []string{domain.NameServer1, domain.NameServer2, domain.NameServer3, domain.NameServer4, domain.NameServer5, domain.NameServer6} {
|
||||||
|
if ns != "" {
|
||||||
|
nss = append(nss, ns)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(nss)
|
||||||
|
foundNameservers := strings.Join(nss, ",")
|
||||||
|
|
||||||
|
expected := []string{}
|
||||||
|
for _, ns := range dc.Nameservers {
|
||||||
|
expected = append(expected, ns.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, domain.Id)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
Reference in New Issue
Block a user