1
0
mirror of https://github.com/StackExchange/dnscontrol.git synced 2024-05-11 05:55:12 +00:00

New Registrar: CSC Global (#827)

* CSC Global Registrar provider

* better error handling. Coding standards.

* Just return the error
This commit is contained in:
Mike Cochrane
2020-09-08 04:00:21 +12:00
committed by GitHub
parent 9554332ef5
commit 9818eb1fca
6 changed files with 308 additions and 0 deletions

View File

@@ -12,6 +12,7 @@
<th class="rotate"><div><span>BIND</span></div></th>
<th class="rotate"><div><span>CLOUDFLAREAPI</span></div></th>
<th class="rotate"><div><span>CLOUDNS</span></div></th>
<th class="rotate"><div><span>CSCGLOBAL</span></div></th>
<th class="rotate"><div><span>DESEC</span></div></th>
<th class="rotate"><div><span>DIGITALOCEAN</span></div></th>
<th class="rotate"><div><span>DNSIMPLE</span></div></th>
@@ -148,6 +149,9 @@
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
<td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></i>
</td>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
@@ -238,6 +242,9 @@
<td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></i>
</td>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
<td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></i>
</td>
@@ -324,6 +331,7 @@
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
<td><i class="fa fa-minus dim"></i></td>
<td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></i>
</td>
@@ -388,6 +396,7 @@
</td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></i>
</td>
@@ -442,6 +451,7 @@
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
<td><i class="fa fa-minus dim"></i></td>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
@@ -516,6 +526,7 @@
<td class="info">
<i class="fa fa-circle-o text-info" aria-hidden="true"></i>
</td>
<td><i class="fa fa-minus dim"></i></td>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
@@ -590,6 +601,7 @@
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
@@ -638,6 +650,7 @@
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
<td><i class="fa fa-minus dim"></i></td>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
@@ -718,6 +731,7 @@
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
<td><i class="fa fa-minus dim"></i></td>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
@@ -778,6 +792,7 @@
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
<td><i class="fa fa-minus dim"></i></td>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
@@ -840,6 +855,7 @@
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
@@ -966,6 +982,7 @@
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
@@ -1018,6 +1035,7 @@
<td class="info">
<i class="fa fa-circle-o text-info" aria-hidden="true"></i>
</td>
<td><i class="fa fa-minus dim"></i></td>
<td class="info">
<i class="fa fa-circle-o text-info" aria-hidden="true"></i>
</td>
@@ -1092,6 +1110,9 @@
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
<td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></i>
</td>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
@@ -1272,6 +1293,7 @@
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
<td><i class="fa fa-minus dim"></i></td>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>

View File

@@ -0,0 +1,37 @@
---
name: CSC Global
title: CSC Global Provider
layout: default
jsId: CSCGLOBAL
---
# CSC Global Provider
DNSControl's CSC Global provider supports being a Registrar. Support for being a DNS Provider is not included, although CSC Global's API does provide for this so it could be implemented in the future.
## Configuration
In your `creds.json` file, you must provide your API key and user/client token. You can optionally provide an comma separated list of email addresses to have CSC Global send updates to.
{% highlight json %}
{
"cscglobal": {
"api-key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"user-token": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
"notification_emails": "test@exmaple.tld,hostmaster@example.tld"
}
}
{% endhighlight %}
## Usage
Example Javascript for `example.tld` and delegated to Route53:
{% highlight js %}
var REG_CSCGLOBAL = NewRegistrar('cscglobal', 'CSCGLOBAL');
var R53 = NewDnsProvider('r53_main', 'ROUTE53');
D("example.tld", REG_CSCGLOBAL, DnsProvider(R53),
A('test','1.2.3.4')
);
{% endhighlight %}
## Activation
To get access to the [CSC Global API](https://www.cscglobal.com/cscglobal/docs/dbs/domainmanager/api-v2/) contact your account manager.

View File

@@ -73,6 +73,7 @@ Maintainers of contributed providers:
* `AXFRDDNS` @hnrgrgr
* `CLOUDNS` @pragmaton
* `CSCGLOBAL` @Air-New-Zealand
* `DESEC` @D3luxee
* `DIGITALOCEAN` @Deraen
* `DNSOVERHTTPS` @mikenz

View File

@@ -9,6 +9,7 @@ import (
_ "github.com/StackExchange/dnscontrol/v3/providers/bind"
_ "github.com/StackExchange/dnscontrol/v3/providers/cloudflare"
_ "github.com/StackExchange/dnscontrol/v3/providers/cloudns"
_ "github.com/StackExchange/dnscontrol/v3/providers/cscglobal"
_ "github.com/StackExchange/dnscontrol/v3/providers/desec"
_ "github.com/StackExchange/dnscontrol/v3/providers/digitalocean"
_ "github.com/StackExchange/dnscontrol/v3/providers/dnsimple"

175
providers/cscglobal/api.go Normal file
View File

@@ -0,0 +1,175 @@
package cscglobal
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"sort"
)
const apiBase = "https://apis.cscglobal.com/dbs/api/v2"
// Api layer for CSC Global
type api struct {
key string
token string
notifyEmails []string
}
type requestParams map[string]string
type errorResponse struct {
Code string `json:"code"`
Description string `json:"description"`
Value string `json:"value,omitempty"`
}
type nsModRequest struct {
Domain string `json:"qualifiedDomainName"`
NameServers []string `json:"nameServers"`
DNSType string `json:"dnsType,omitempty"`
Notifications struct {
Enabled bool `json:"enabled,omitempty"`
Emails []string `json:"additionalNotificationEmails,omitempty"`
} `json:"notifications"`
ShowPrice bool `json:"showPrice,omitempty"`
CustomFields []string `json:"customFields,omitempty"`
}
type nsModRequestResult struct {
Result struct {
Domain string `json:"qualifiedDomainName"`
Status struct {
Code string `json:"code"`
Message string `json:"message"`
AdditionalInformation string `json:"additionalInformation"`
UUID string `json:"uuid"`
} `json:"status"`
} `json:"result"`
}
type domainRecord struct {
Nameserver []string `json:"nameservers"`
}
func (c *api) getNameservers(domain string) ([]string, error) {
var bodyString, err = c.get("/domains/" + domain)
if err != nil {
return nil, err
}
var dr domainRecord
json.Unmarshal(bodyString, &dr)
ns := []string{}
for _, nameserver := range dr.Nameserver {
ns = append(ns, nameserver)
}
sort.Strings(ns)
return ns, nil
}
func (c *api) updateNameservers(ns []string, domain string) error {
req := nsModRequest{
Domain: domain,
NameServers: ns,
DNSType: "OTHER_DNS",
ShowPrice: false,
}
if c.notifyEmails != nil {
req.Notifications.Enabled = true
req.Notifications.Emails = c.notifyEmails
}
req.CustomFields = []string{}
requestBody, err := json.MarshalIndent(req, "", " ")
if err != nil {
return err
}
bodyString, err := c.put("/domains/nsmodification", requestBody)
if err != nil {
return fmt.Errorf("CSC Global: Error update NS : %w", err)
}
var res nsModRequestResult
json.Unmarshal(bodyString, &res)
if res.Result.Status.Code != "SUBMITTED" {
return fmt.Errorf("CSC Global: Error update NS Code: %s Message: %s AdditionalInfo: %s", res.Result.Status.Code, res.Result.Status.Message, res.Result.Status.AdditionalInformation)
}
return nil
}
func (c *api) put(endpoint string, requestBody []byte) ([]byte, error) {
client := &http.Client{}
req, _ := http.NewRequest("PUT", apiBase+endpoint, bytes.NewReader(requestBody))
// Add headers
req.Header.Add("apikey", c.key)
req.Header.Add("Authorization", "Bearer "+c.token)
req.Header.Add("Accept", "application/json")
req.Header.Add("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return nil, err
}
bodyString, _ := ioutil.ReadAll(resp.Body)
if resp.StatusCode == 200 {
return bodyString, nil
}
// Got a error response from API, see if it's json format
var errResp errorResponse
err = json.Unmarshal(bodyString, &errResp)
if err != nil {
// Some error messages are plain text
return nil, fmt.Errorf("CSC Global API error: %s URL: %s%s",
bodyString,
req.Host, req.URL.RequestURI())
}
return nil, fmt.Errorf("CSC Global API error code: %s description: %s URL: %s%s",
errResp.Code, errResp.Description,
req.Host, req.URL.RequestURI())
}
func (c *api) get(endpoint string) ([]byte, error) {
client := &http.Client{}
req, _ := http.NewRequest("GET", apiBase+endpoint, nil)
// Add headers
req.Header.Add("apikey", c.key)
req.Header.Add("Authorization", "Bearer "+c.token)
req.Header.Add("Accept", "application/json")
resp, err := client.Do(req)
if err != nil {
return nil, err
}
bodyString, _ := ioutil.ReadAll(resp.Body)
if resp.StatusCode == 200 {
return bodyString, nil
}
if resp.StatusCode == 400 {
// 400, error message is in the body as plain text
return nil, fmt.Errorf("CSC Global API error: %w URL: %s%s",
bodyString,
req.Host, req.URL.RequestURI())
}
// Got a json error response from API
var errResp errorResponse
err = json.Unmarshal(bodyString, &errResp)
if err != nil {
return nil, err
}
return nil, fmt.Errorf("CSC Global API error code: %s description: %s URL: %s%s",
errResp.Code, errResp.Description,
req.Host, req.URL.RequestURI())
}

View File

@@ -0,0 +1,72 @@
package cscglobal
import (
"fmt"
"sort"
"strings"
"github.com/StackExchange/dnscontrol/v3/models"
"github.com/StackExchange/dnscontrol/v3/providers"
)
/*
CSC Global Registrar:
Info required in `creds.json`:
- api-key Api Key
- user-token User Token
- notification_emails (optional) Comma seperated list of email addresses to send notifications to
*/
func init() {
providers.RegisterRegistrarType("CSCGLOBAL", newCscGlobal)
}
func newCscGlobal(m map[string]string) (providers.Registrar, error) {
api := &api{}
api.key, api.token = m["api-key"], m["user-token"]
if api.key == "" || api.token == "" {
return nil, fmt.Errorf("missing CSC Global api-key and/or user-token")
}
if m["notification_emails"] != "" {
api.notifyEmails = strings.Split(m["notification_emails"], ",")
}
return api, nil
}
// GetRegistrarCorrections gathers corrections that would being n to match dc.
func (c *api) 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 {
if ns.Name[len(ns.Name)-1] == '.' {
// When this code was written ns.Name never included a single trailing dot.
// If that changes, the code should change too.
return nil, fmt.Errorf("Name server includes a trailing dot, has the API changed?")
}
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, dc.Name)
},
},
}, nil
}
return nil, nil
}