Skip to content

Commit

Permalink
NEW PROVIDER: Mythic Beasts DNS (#2528)
Browse files Browse the repository at this point in the history
Co-authored-by: Tom Limoncelli <tlimoncelli@stackoverflow.com>
  • Loading branch information
tomfitzhenry and tlimoncelli committed Aug 25, 2023
1 parent c69bd43 commit 5416793
Show file tree
Hide file tree
Showing 10 changed files with 213 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ changelog:
regexp: "(?i)^.*(major|new provider|feature)[(\\w)]*:+.*$"
order: 1
- title: 'Provider-specific changes:'
regexp: "(?i)((akamaiedge|autodns|axfrd|azure|bind|cloudflare|cloudflareapi_old|cloudns|cscglobal|desec|digitalocean|dnsimple|dnsmadeeasy|doh|domainnameshop|easyname|exoscale|gandi|gcloud|gcore|hedns|hetzner|hexonet|hostingde|inwx|linode|loopia|luadns|msdns|namecheap|namedotcom|netcup|netlify|ns1|opensrs|oracle|ovh|packetframe|porkbun|powerdns|route53|rwth|softlayer|transip|vultr).*:)+.*"
regexp: "(?i)((akamaiedge|autodns|axfrd|azure|bind|cloudflare|cloudflareapi_old|cloudns|cscglobal|desec|digitalocean|dnsimple|dnsmadeeasy|doh|domainnameshop|easyname|exoscale|gandi|gcloud|gcore|hedns|hetzner|hexonet|hostingde|inwx|linode|loopia|luadns|msdns|mythicbeasts|namecheap|namedotcom|netcup|netlify|ns1|opensrs|oracle|ovh|packetframe|porkbun|powerdns|route53|rwth|softlayer|transip|vultr).*:)+.*"
order: 2
- title: 'Deprecation warnings:'
regexp: "(?i)^.*Deprecate[(\\w)]*:+.*$"
Expand Down
1 change: 1 addition & 0 deletions OWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ providers/linode @koesie10
providers/loopia @systemcrash
providers/luadns @riku22
providers/msdns @tlimoncelli
providers/mythicbeasts @tomfitzhenry
providers/namecheap @willpower232
# providers/namedotcom NEEDS VOLUNTEER
providers/netcup @kordianbruck
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Currently supported DNS providers:
- Loopia
- LuaDNS
- Microsoft Windows Server DNS Server
- Mythic Beasts
- Namecheap
- Name.com
- Netcup
Expand Down
1 change: 1 addition & 0 deletions documentation/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
* [Loopia](providers/loopia.md)
* [LuaDNS](providers/luadns.md)
* [Microsoft DNS Server on Microsoft Windows Server](providers/msdns.md)
* [Mythic Beasts](providers/mythicbeasts.md)
* [Namecheap](providers/namecheap.md)
* [Name.com](providers/namedotcom.md)
* [Netcup](providers/netcup.md)
Expand Down
1 change: 1 addition & 0 deletions documentation/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ If a feature is definitively not supported for whatever reason, we would also li
| [`LOOPIA`](providers/loopia.md) |||||||||||||||||||
| [`LUADNS`](providers/luadns.md) |||||||||||||||||||
| [`MSDNS`](providers/msdns.md) |||||||||||||||||||
| [`MYTHICBEASTS`](providers/mythicbeasts.md) |||||||||||||||||||
| [`NAMECHEAP`](providers/namecheap.md) |||||||||||||||||||
| [`NAMEDOTCOM`](providers/namedotcom.md) |||||||||||||||||||
| [`NETCUP`](providers/netcup.md) |||||||||||||||||||
Expand Down
39 changes: 39 additions & 0 deletions documentation/providers/mythicbeasts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
This is the provider for [Mythic Beasts](https://www.mythic-beasts.com/) using its [Primary DNS API v2](https://www.mythic-beasts.com/support/api/dnsv2).

## Configuration

To use this provider, add an entry to `creds.json` with `TYPE` set to `MYTHICBEASTS` along with a Mythic Beasts API key ID and secret.

Example:

{% code title="creds.json" %}
```json
{
"mythicbeasts": {
"TYPE": "MYTHICBEASTS",
"keyID": "xxxxxxx",
"secret": "xxxxxx"
}
}
```
{% endcode %}

## Usage

For each domain:

* Domains must be added in the [web UI](https://www.mythic-beasts.com/customer/domains), and have DNS enabled.
* In Mythic Beasts' DNS management web UI, new domains will have set a default DNS template of "Mythic Beasts nameservers only". You must set this to "None".

An example configuration:

{% code title="dnsconfig.js" %}
```javascript
var REG_NONE = NewRegistrar("none");
var DSP_MYTHIC = NewDnsProvider("mythicbeasts");

D("example.com", REG_NONE, DnsProvider(DSP_MYTHIC),
A("test", "1.2.3.4")
);
```
{% endcode %}
6 changes: 6 additions & 0 deletions integrationTest/providers.json
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,12 @@
"domain": "$MSDNS_DOMAIN",
"pssession": "$MSDNS_PSSESSION"
},
"MYTHICBEASTS": {
"TYPE": "MYTHICBEASTS",
"keyID": "$MYTHICBEASTS_KEYID",
"secret": "$MYTHICBEASTS_SECRET",
"domain": "$MYTHICBEASTS_DOMAIN"
},
"NAMECHEAP": {
"BaseURL": "$NAMECHEAP_BASEURL",
"TYPE": "NAMECHEAP",
Expand Down
1 change: 1 addition & 0 deletions providers/_all/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
_ "github.com/StackExchange/dnscontrol/v4/providers/loopia"
_ "github.com/StackExchange/dnscontrol/v4/providers/luadns"
_ "github.com/StackExchange/dnscontrol/v4/providers/msdns"
_ "github.com/StackExchange/dnscontrol/v4/providers/mythicbeasts"
_ "github.com/StackExchange/dnscontrol/v4/providers/namecheap"
_ "github.com/StackExchange/dnscontrol/v4/providers/namedotcom"
_ "github.com/StackExchange/dnscontrol/v4/providers/netcup"
Expand Down
15 changes: 15 additions & 0 deletions providers/mythicbeasts/auditrecords.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package mythicbeasts

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.TxtHasDoubleQuotes)
return nil
}
147 changes: 147 additions & 0 deletions providers/mythicbeasts/mythicbeastsProvider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Package mythicbeasts provides a provider for managing zones in Mythic Beasts.
//
// This package uses the Primary DNS API v2, as described in https://www.mythic-beasts.com/support/api/dnsv2
package mythicbeasts

import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"

"github.com/StackExchange/dnscontrol/v4/models"
"github.com/StackExchange/dnscontrol/v4/pkg/diff2"
"github.com/StackExchange/dnscontrol/v4/providers"
"github.com/miekg/dns"
)

// mythicBeastsDefaultNS lists the default nameservers, per https://www.mythic-beasts.com/support/domains/nameservers.
var mythicBeastsDefaultNS = []string{
"ns1.mythic-beasts.com",
"ns2.mythic-beasts.com",
}

// mythicBeastsProvider is the handle for this provider.
type mythicBeastsProvider struct {
secret string
keyID string
client *http.Client
}

var features = providers.DocumentationNotes{
providers.CanGetZones: providers.Can(),
providers.CanUseAlias: providers.Cannot(),
providers.CanUseCAA: providers.Can(),
providers.CanUseLOC: providers.Cannot(),
providers.CanUsePTR: providers.Can(),
providers.CanUseSRV: providers.Can(),
providers.CanUseTLSA: providers.Can(),
providers.DocCreateDomains: providers.Cannot("Requires domain registered through Web UI"),
providers.DocDualHost: providers.Can(),
providers.DocOfficiallySupported: providers.Cannot(),
}

func init() {
fns := providers.DspFuncs{
Initializer: newDsp,
RecordAuditor: AuditRecords,
}
providers.RegisterDomainServiceProviderType("MYTHICBEASTS", fns, features)
}

func newDsp(conf map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
if conf["keyID"] == "" {
return nil, fmt.Errorf("missing Mythic Beasts auth keyID")
}
if conf["secret"] == "" {
return nil, fmt.Errorf("missing Mythic Beasts auth secret")
}
return &mythicBeastsProvider{
keyID: conf["keyID"],
secret: conf["secret"],
client: &http.Client{},
}, nil
}

func (n *mythicBeastsProvider) httpRequest(method, url string, body io.Reader) (*http.Response, error) {
req, err := http.NewRequest(method, "https://api.mythic-beasts.com/dns/v2"+url, body)
if err != nil {
return nil, err
}
// https://www.mythic-beasts.com/support/api/auth
req.SetBasicAuth(n.keyID, n.secret)
req.Header.Add("Content-Type", "text/dns")
req.Header.Add("Accept", "text/dns")
return n.client.Do(req)
}

// GetZoneRecords gets the records of a zone and returns them in RecordConfig format.
func (n *mythicBeastsProvider) GetZoneRecords(domain string, meta map[string]string) (models.Records, error) {
resp, err := n.httpRequest("GET", "/zones/"+domain+"/records", nil)
if err != nil {
return nil, err
}
if got, want := resp.StatusCode, 200; got != want {
body, _ := ioutil.ReadAll(resp.Body)
return nil, fmt.Errorf("got HTTP %v, want %v: %v", got, want, string(body))
}
return zoneFileToRecords(resp.Body, domain)
}

func zoneFileToRecords(r io.Reader, origin string) (models.Records, error) {
zp := dns.NewZoneParser(r, origin, origin)
var records []*models.RecordConfig
for rr, ok := zp.Next(); ok; rr, ok = zp.Next() {
rec, err := models.RRtoRC(rr, origin)
if err != nil {
return nil, err
}
records = append(records, &rec)
}

if err := zp.Err(); err != nil {
return nil, fmt.Errorf("parsing zone for %v: %w", origin, err)
}
return records, nil
}

// GetZoneRecordsCorrections returns a list of corrections that will turn existing records into dc.Records.
func (n *mythicBeastsProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, actual models.Records) ([]*models.Correction, error) {
msgs, changes, err := diff2.ByZone(actual, dc, nil)
if err != nil {
return nil, err
}

var corrections []*models.Correction
if changes {
corrections = append(corrections,
&models.Correction{
Msg: strings.Join(msgs, "\n"),
F: func() error {
var b strings.Builder
for _, rr := range dc.Records {
fmt.Fprintf(&b, "%v\n", rr.ToRR().String())
}

resp, err := n.httpRequest("PUT", "/zones/"+dc.Name+"/records", strings.NewReader(b.String()))
if err != nil {
return err
}
if got, want := resp.StatusCode, 200; got != want {
body, _ := ioutil.ReadAll(resp.Body)
return fmt.Errorf("got HTTP %v, want %v: %v", got, want, string(body))
}
return nil
},
})
}

return corrections, nil
}

// GetNameservers returns the nameservers for a domain.
func (n *mythicBeastsProvider) GetNameservers(domainName string) ([]*models.Nameserver, error) {
return models.ToNameservers(mythicBeastsDefaultNS)
}

0 comments on commit 5416793

Please sign in to comment.