1
0
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:
Brian Hartvigsen
2021-10-04 11:01:38 -06:00
committed by GitHub
parent 7f071b4ce8
commit 156c684be6
7 changed files with 274 additions and 4 deletions

1
OWNERS
View File

@ -10,6 +10,7 @@ providers/doh @mikenz
providers/digitalocean @Deraen
providers/dnsimple @aeden
providers/dnsmadeeasy @vojtad
providers/easyname @tresni
providers/gandi_v5 @TomOnTime
# providers/gcloud
providers/hedns @rblenkinsopp

View File

@ -23,7 +23,7 @@ Currently supported DNS providers:
- BIND
- ClouDNS
- Cloudflare
- DNSOVERHTTPS
- deSEC
- DNS Made Easy
- DNSimple
- DigitalOcean
@ -35,7 +35,6 @@ Currently supported DNS providers:
- hosting.de
- Hurricane Electric DNS
- INWX
- Internet.bs
- Linode
- Microsoft Windows Server DNS Server
- NS1
@ -44,13 +43,27 @@ Currently supported DNS providers:
- Netcup
- OVH
- OctoDNS
- OpenSRS
- Oracle Cloud
- PowerDNS
- SoftLayer
- TransIP
- 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
and subdomains across multiple registrars and DNS providers.

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

View File

@ -80,6 +80,7 @@ Maintainers of contributed providers:
* `DNSOVERHTTPS` @mikenz
* `DNSIMPLE` @aeden
* `DNSMADEEASY` @vojtad
* `EASYNAME` @tresni
* `EXOSCALE` @pierre-emmanuelJ
* `GANDI_V5` @TomOnTime
* `HEDNS` @rblenkinsopp

View File

@ -16,6 +16,7 @@ import (
_ "github.com/StackExchange/dnscontrol/v3/providers/dnsimple"
_ "github.com/StackExchange/dnscontrol/v3/providers/dnsmadeeasy"
_ "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/gandiv5"
_ "github.com/StackExchange/dnscontrol/v3/providers/gcloud"

140
providers/easyname/api.go Normal file
View 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
}

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