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/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

View File

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

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 * `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

View File

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