Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NEW PROVIDER: CNR (test) #3222

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/pr_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ jobs:
Write-Host "Integration test providers: $Providers"
echo "integration_test_providers=$(ConvertTo-Json -InputObject $Providers -Compress)" >> $env:GITHUB_OUTPUT
env:
PROVIDERS: "['AZURE_DNS','BIND','BUNNY_DNS','CLOUDFLAREAPI','CLOUDNS','DIGITALOCEAN','GANDI_V5','GCLOUD','HEDNS','HEXONET','HUAWEICLOUD','INWX','NAMEDOTCOM','NS1','POWERDNS','ROUTE53','SAKURACLOUD','TRANSIP']"
PROVIDERS: "['AZURE_DNS','BIND','BUNNY_DNS','CLOUDFLAREAPI','CLOUDNS','CNR','DIGITALOCEAN','GANDI_V5','GCLOUD','HEDNS','HEXONET','HUAWEICLOUD','INWX','NAMEDOTCOM','NS1','POWERDNS','ROUTE53','SAKURACLOUD','TRANSIP']"
ENV_CONTEXT: ${{ toJson(env) }}
VARS_CONTEXT: ${{ toJson(vars) }}
SECRETS_CONTEXT: ${{ toJson(secrets) }}
Expand All @@ -111,6 +111,7 @@ jobs:
BUNNY_DNS_DOMAIN: ${{ vars.BUNNY_DNS_DOMAIN }}
CLOUDFLAREAPI_DOMAIN: ${{ vars.CLOUDFLAREAPI_DOMAIN }}
CLOUDNS_DOMAIN: ${{ vars.CLOUDNS_DOMAIN }}
CNR_DOMAIN: ${{ vars.CNR_DOMAIN }}
CSCGLOBAL_DOMAIN: ${{ vars.CSCGLOBAL_DOMAIN }}
DIGITALOCEAN_DOMAIN: ${{ vars.DIGITALOCEAN_DOMAIN }}
GANDI_V5_DOMAIN: ${{ vars.GANDI_V5_DOMAIN }}
Expand Down Expand Up @@ -146,6 +147,10 @@ jobs:
CSCGLOBAL_APIKEY: ${{ secrets.CSCGLOBAL_APIKEY }}
CSCGLOBAL_USERTOKEN: ${{ secrets.CSCGLOBAL_USERTOKEN }}
#
CNR_UID: ${{ secrets.CNR_UID }}
CNR_PW: ${{ secrets.CNR_PW }}
CNR_ENTITY: ${{ secrets.CNR_ENTITY }}
#
DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }}
#
GANDI_V5_APIKEY: ${{ secrets.GANDI_V5_APIKEY }}
Expand Down
1 change: 1 addition & 0 deletions OWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ providers/bind @tlimoncelli
providers/bunnydns @ppmathis
providers/cloudflare @tresni
providers/cloudns @pragmaton
providers/cnr @KaiSchwarz-cnic
providers/cscglobal @mikenz
providers/desec @D3luxee
providers/digitalocean @Deraen
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Currently supported DNS providers:
- Bunny DNS
- Cloudflare
- ClouDNS
- CentralNic Reseller (CNR) - formerly RRPProxy
- deSEC
- DigitalOcean
- DNS Made Easy
Expand Down Expand Up @@ -66,6 +67,7 @@ Currently supported Domain Registrars:

- AWS Route 53
- CSC Global
- CentralNic Reseller (formerly RRPProxy)
- DNSOVERHTTPS
- Dynadot
- easyname
Expand Down
112 changes: 112 additions & 0 deletions documentation/provider/cnr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
CentralNic Reseller (CNR), formerly known as RRPProxy, is a prominent provider of domain registration and DNS solutions. Trusted by individuals, service providers, and registrars around the world, CNR is recognized for its cutting-edge technology, exceptional performance, and reliable uptime.

Our advanced DNS expertise is integral to our offering. With CentralNic Reseller, you benefit from a leading DNS platform that features robust DNS automation, DNSSEC for enhanced security, and PremiumDNS via our Anycast Network. Additionally, our platform supports a comprehensive set of features, as detailed by DNSControl.

This is based on API documents found at [https://kb.centralnicreseller.com/api/api-commands/api-command-reference#cat-dynamicdns](https://kb.centralnicreseller.com/api/api-commands/api-command-reference#cat-dynamicdns)

## Configuration

To use this provider, add an entry to `creds.json` with `TYPE` set to `CNR`
along with your CentralNic Reseller login data.

Example:

{% code title="creds.json" %}
```json
{
"CNR": {
"TYPE": "CNR",
"apilogin": "your-cnr-account-id",
"apipassword": "your-cnr-account-password",
"apientity": "LIVE", // for the LIVE system; use "OTE" for the OT&E system
"debugmode": "0", // set it to "1" to get debug output of the communication with our Backend System API
}
}
```
{% endcode %}

Here a working example for our OT&E System:

{% code title="creds.json" %}
```json
{
"CNR": {
"TYPE": "CNR",
"apilogin": "YourUserName",
"apipassword": "YourPassword",
"apientity": "OTE",
"debugmode": "0"
}
}
```
{% endcode %}

{% hint style="info" %}
**NOTE**: The above credentials are known to the public.
{% endhint %}

With the above CentralNic Reseller entry in `creds.json`, you can run the
integration tests as follows:

```shell
dnscontrol get-zones --format=nameonly cnr CNR all
```
```shell
# Review the output. Pick one domain and set CNR_DOMAIN.
export CNR_DOMAIN=yodream.com # Pick a domain name.
export CNR_ENTITY=OTE
export CNR_UID=test.user
export CNR_PW=test.passw0rd
cd integrationTest # NOTE: Not needed if already in that subdirectory
go test -v -verbose -provider CNR
```

## Usage

Here's an example DNS Configuration `dnsconfig.js` using our provider module.
Even though it shows how you use us as Domain Registrar AND DNS Provider, we don't force you to do that.
You are free to decide if you want to use both of our provider technology or just one of them.

{% code title="dnsconfig.js" %}
```javascript
var REG_CNR = NewRegistrar("CNR");
var DSP_CNR = NewDnsProvider("CNR");

// Set Default TTL for all RR to reflect our Backend API Default
// If you use additional DNS Providers, configure a default TTL
// per domain using the domain modifier DefaultTTL instead.
// also check this issue for [NAMESERVER TTL](https://github.com/StackExchange/dnscontrol/issues/176).
DEFAULTS(
{"ns_ttl":"3600"},
DefaultTTL(3600)
);

D("example.com", REG_CNR, DnsProvider(DSP_CNR),
NAMESERVER("ns1.rrpproxy.net"),
NAMESERVER("ns2.rrpproxy.net"),
NAMESERVER("ns3.rrpproxy.net"),
NAMESERVER("ns4.rrpproxy.net"),
A("elk1", "10.190.234.178"),
A("test", "56.123.54.12"),
END);
```
{% endcode %}

## Metadata

This provider does not recognize any special metadata fields unique to CentralNic Reseller (CNR).

## get-zones

`dnscontrol get-zones` is implemented for this provider. The list
includes both basic and premier zones.

## New domains

If a dnszone does not exist in your CNR account, DNSControl will *not* automatically add it with the `dnscontrol push` or `dnscontrol preview` command. You'll need to do that via the control panel manually or using the command `dnscontrol create-domains`.
This is because it could lead to unwanted costs on customer-side that we want to avoid.

## Debug Mode

As shown in the configuration examples above, this can be activated on demand and it can be used to check the API commands send to our system.
In general this is thought for our purpose to have an easy way to dive into issues. But if you're interested what's going on, feel free to activate it.
1 change: 1 addition & 0 deletions documentation/providers.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Service Providers

Check notice on line 1 in documentation/providers.md

View workflow job for this annotation

GitHub Actions / check-git-status

File: documentation/providers.md

## Provider Features

Expand All @@ -23,6 +23,7 @@
| [`BUNNY_DNS`](provider/bunny_dns.md) | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | ❔ | ❌ | ❌ | ✅ | ❌ | ✅ | ❌ | ❔ | ❌ | ❌ | ❌ | ❔ | ❔ | ❌ | ✅ | ✅ |
| [`CLOUDFLAREAPI`](provider/cloudflareapi.md) | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❔ | ✅ | ❌ | ✅ | ✅ | ❔ | ✅ | ✅ | ✅ | ✅ | ❔ | ❔ | ❔ | ❌ | ❌ | ✅ | ✅ |
| [`CLOUDNS`](provider/cloudns.md) | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ❔ | ✅ | ❔ | ✅ | ❔ | ✅ | ✅ | ❔ | ✅ | ❔ | ❔ | ✅ | ❔ | ❔ | ✅ | ✅ |
| [`CNR`](provider/cnr.md) | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❔ | ❔ | ❔ | ❔ | ✅ | ❔ | ✅ | ❔ | ❔ | ✅ | ❔ | ❔ | ❔ | ❔ | ✅ | ✅ | ❔ |
| [`CSCGLOBAL`](provider/cscglobal.md) | ✅ | ✅ | ✅ | ✅ | ❔ | ✅ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ✅ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❌ | ✅ |
| [`DESEC`](provider/desec.md) | ❌ | ✅ | ❌ | ✅ | ❔ | ✅ | ✅ | ✅ | ❔ | ✅ | ✅ | ❔ | ✅ | ✅ | ✅ | ✅ | ✅ | ❔ | ❔ | ✅ | ❔ | ✅ | ✅ |
| [`DIGITALOCEAN`](provider/digitalocean.md) | ❌ | ✅ | ❌ | ✅ | ❔ | ✅ | ❔ | ❔ | ❌ | ❔ | ❔ | ❔ | ✅ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ✅ | ✅ |
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ require (
require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0
github.com/G-Core/gcore-dns-sdk-go v0.2.9
github.com/centralnicgroup-opensource/rtldev-middleware-go-sdk/v5 v5.0.0
github.com/fatih/color v1.18.0
github.com/fbiville/markdown-table-formatter v0.3.0
github.com/go-acme/lego/v4 v4.20.2
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ github.com/centralnicgroup-opensource/rtldev-middleware-go-sdk/v4 v4.0.7 h1:Jk7u
github.com/centralnicgroup-opensource/rtldev-middleware-go-sdk/v4 v4.0.7/go.mod h1:FnQtD0+Q/1NZxi0eEWN+3ZRyMsE9vzSB3YjyunkbKD0=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/centralnicgroup-opensource/rtldev-middleware-go-sdk/v5 v5.0.0 h1:45FDlPw2mCKrP3C3i0mACQpnG14k3z6ZhDX853idMHw=
github.com/centralnicgroup-opensource/rtldev-middleware-go-sdk/v5 v5.0.0/go.mod h1:gDHPM5Nia+C/Q4Uw5rn9i+OIP3S06WUe7RdCpNP2C+E=
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
Expand Down
8 changes: 8 additions & 0 deletions integrationTest/providers.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,14 @@
"notification_emails": "$CSCGLOBAL_NOTIFICATION",
"user-token": "$CSCGLOBAL_USERTOKEN"
},
"CNR": {
"TYPE": "CNR",
"apientity": "$CNR_ENTITY",
"apilogin": "$CNR_UID",
"apipassword": "$CNR_PW",
"debugmode": "$CNR_DEBUGMODE",
"domain": "$CNR_DOMAIN"
},
"DESEC": {
"TYPE": "DESEC",
"auth-token": "$DESEC_TOKEN",
Expand Down
1 change: 1 addition & 0 deletions providers/_all/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
_ "github.com/StackExchange/dnscontrol/v4/providers/azureprivatedns"
_ "github.com/StackExchange/dnscontrol/v4/providers/bind"
_ "github.com/StackExchange/dnscontrol/v4/providers/bunnydns"
_ "github.com/StackExchange/dnscontrol/v4/providers/cnr"
_ "github.com/StackExchange/dnscontrol/v4/providers/cloudflare"
_ "github.com/StackExchange/dnscontrol/v4/providers/cloudns"
_ "github.com/StackExchange/dnscontrol/v4/providers/cscglobal"
Expand Down
21 changes: 21 additions & 0 deletions providers/cnr/auditrecords.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package cnr

import (
"github.com/StackExchange/dnscontrol/v4/models"
"github.com/StackExchange/dnscontrol/v4/pkg/rejectif"
)

// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
a := rejectif.Auditor{}

a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2021-10-01

a.Add("TXT", rejectif.TxtHasDoubleQuotes) // Last verified 2023-11-30

a.Add("SRV", rejectif.SrvHasNullTarget) // Last verified 2020-12-28

return a.Audit(records)
}
81 changes: 81 additions & 0 deletions providers/cnr/cnrProvider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Package CNR implements a registrar that uses the CNR api to set name servers. It will self register it's providers when imported.
package cnr

import (
"encoding/json"
"fmt"

"github.com/StackExchange/dnscontrol/v4/providers"
cnrcl "github.com/centralnicgroup-opensource/rtldev-middleware-go-sdk/v5/apiclient"
)

// GoReleaser: version
var (
version = "dev"
)

// CNRClient describes a connection to the CNR API.
type CNRClient struct {
APILogin string
APIPassword string
APIEntity string
client *cnrcl.APIClient
}

var features = providers.DocumentationNotes{
// The default for unlisted capabilities is 'Cannot'.
// See providers/capabilities.go for the entire list of capabilities.
providers.CanGetZones: providers.Can(),
providers.CanConcur: providers.Can(),
providers.CanUseAlias: providers.Cannot("Not supported. You may use CNAME records instead. An Alternative solution is planned."),
providers.CanUseCAA: providers.Can(),
providers.CanUseLOC: providers.Unimplemented(),
providers.CanUsePTR: providers.Can(),
providers.CanUseSRV: providers.Can("SRV records with empty targets are not supported"),
providers.CanUseTLSA: providers.Can(),
providers.DocCreateDomains: providers.Can(),
providers.DocDualHost: providers.Can(),
providers.DocOfficiallySupported: providers.Cannot("Actively maintained provider module."),
}

func newProvider(conf map[string]string) (*CNRClient, error) {
api := &CNRClient{
client: cnrcl.NewAPIClient(),
}
api.client.SetUserAgent("DNSControl", version)
api.APILogin, api.APIPassword, api.APIEntity = conf["apilogin"], conf["apipassword"], conf["apientity"]
if conf["debugmode"] == "1" {
api.client.EnableDebugMode()
}
if api.APIEntity != "OTE" && api.APIEntity != "LIVE" {
return nil, fmt.Errorf("wrong api system entity used. use \"OTE\" for OT&E system or \"LIVE\" for Live system")
}
if api.APIEntity == "OTE" {
api.client.UseOTESystem()
}
if api.APILogin == "" || api.APIPassword == "" {
return nil, fmt.Errorf("missing login credentials apilogin or apipassword")
}
api.client.SetCredentials(api.APILogin, api.APIPassword)
return api, nil
}

func newReg(conf map[string]string) (providers.Registrar, error) {
return newProvider(conf)
}

func newDsp(conf map[string]string, meta json.RawMessage) (providers.DNSServiceProvider, error) {
return newProvider(conf)
}

func init() {
const providerName = "CNR"
const providerMaintainer = "@KaiSchwarz-cnic"
fns := providers.DspFuncs{
Initializer: newDsp,
RecordAuditor: AuditRecords,
}
providers.RegisterRegistrarType(providerName, newReg)
providers.RegisterDomainServiceProviderType(providerName, fns, features)
providers.RegisterMaintainer(providerName, providerMaintainer)
}
55 changes: 55 additions & 0 deletions providers/cnr/domains.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package cnr

// EnsureZoneExists returns an error
// * if access to dnszone is not allowed (not authorized) or
// * if it doesn't exist and creating it fails
func (n *CNRClient) EnsureZoneExists(domain string) error {
r := n.client.Request(map[string]interface{}{
"COMMAND": "StatusDNSZone",
"DNSZONE": domain,
})
code := r.GetCode()
if code == 545 {
command := map[string]interface{}{
"COMMAND": "AddDNSZone",
"DNSZONE": domain,
}
if n.APIEntity == "OTE" {
command["SOATTL"] = "33200"
command["SOASERIAL"] = "0000000000"
}
// Create the zone
r = n.client.Request(command)
if !r.IsSuccess() {
return n.GetCNRApiError("Failed to create not existing zone ", domain, r)
}
} else if code == 531 {
return n.GetCNRApiError("Not authorized to manage dnszone", domain, r)
} else if r.IsError() || r.IsTmpError() {
return n.GetCNRApiError("Error while checking status of dnszone", domain, r)
}
return nil
}

// ListZones lists all the
func (n *CNRClient) ListZones() ([]string, error) {
var zones []string

// Basic

rs := n.client.RequestAllResponsePages(map[string]string{
"COMMAND": "QueryDNSZoneList",
})
for _, r := range rs {
if r.IsError() {
return nil, n.GetCNRApiError("Error while QueryDNSZoneList", "Basic", &r)
}
zoneColumn := r.GetColumn("DNSZONE")
if zoneColumn != nil {
//return nil, fmt.Errorf("failed getting DNSZONE BASIC column")
zones = append(zones, zoneColumn.GetData()...)
}
}

return zones, nil
}
12 changes: 12 additions & 0 deletions providers/cnr/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package cnr

import (
"fmt"

"github.com/centralnicgroup-opensource/rtldev-middleware-go-sdk/v5/response"
)

// GetCNRApiError returns an error including API error code and error description.
func (n *CNRClient) GetCNRApiError(format string, objectid string, r *response.Response) error {
return fmt.Errorf(format+" %q. [%v %s]", objectid, r.GetCode(), r.GetDescription())
}
Loading
Loading