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

Add RWTH provider (#1629)

* Add RWTH provider

* fix Owners order

* Reorganize RWTH Provider

* Fix staticcheck and code style issues

Co-authored-by: Tom Limoncelli <tlimoncelli@stackoverflow.com>
This commit is contained in:
MisterErwin
2022-08-04 20:40:27 +02:00
committed by GitHub
parent ba747fa5a9
commit 7865e37c8f
14 changed files with 539 additions and 2 deletions

1
OWNERS
View File

@ -32,6 +32,7 @@ providers/ns1 @costasd
providers/opensrs @philhug providers/opensrs @philhug
providers/oracle @kallsyms providers/oracle @kallsyms
providers/route53 @tresni providers/route53 @tresni
providers/rwth @mistererwin
# providers/softlayer NEEDS VOLUNTEER # providers/softlayer NEEDS VOLUNTEER
providers/vultr @pgaskin providers/vultr @pgaskin
providers/ovh @masterzen providers/ovh @masterzen

View File

@ -48,6 +48,7 @@ Currently supported DNS providers:
- Oracle Cloud - Oracle Cloud
- Packetframe - Packetframe
- PowerDNS - PowerDNS
- RWTH DNS-Admin
- SoftLayer - SoftLayer
- TransIP - TransIP
- Vultr - Vultr

View File

@ -43,6 +43,7 @@
<th class="rotate"><div><span>PACKETFRAME</span></div></th> <th class="rotate"><div><span>PACKETFRAME</span></div></th>
<th class="rotate"><div><span>POWERDNS</span></div></th> <th class="rotate"><div><span>POWERDNS</span></div></th>
<th class="rotate"><div><span>ROUTE53</span></div></th> <th class="rotate"><div><span>ROUTE53</span></div></th>
<th class="rotate"><div><span>RWTH</span></div></th>
<th class="rotate"><div><span>SOFTLAYER</span></div></th> <th class="rotate"><div><span>SOFTLAYER</span></div></th>
<th class="rotate"><div><span>TRANSIP</span></div></th> <th class="rotate"><div><span>TRANSIP</span></div></th>
<th class="rotate"><div><span>VULTR</span></div></th> <th class="rotate"><div><span>VULTR</span></div></th>
@ -174,6 +175,9 @@
<td class="danger"> <td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></i> <i class="fa fa-times text-danger" aria-hidden="true"></i>
</td> </td>
<td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></i>
</td>
</tr> </tr>
<tr> <tr>
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Can manage and serve DNS zones">DNS Provider</th> <th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Can manage and serve DNS zones">DNS Provider</th>
@ -300,6 +304,9 @@
<td class="success"> <td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i> <i class="fa fa-check text-success" aria-hidden="true"></i>
</td> </td>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
</tr> </tr>
<tr> <tr>
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="The provider has registrar capabilities to set nameservers for zones">Registrar</th> <th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="The provider has registrar capabilities to set nameservers for zones">Registrar</th>
@ -426,6 +433,9 @@
<td class="danger"> <td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></i> <i class="fa fa-times text-danger" aria-hidden="true"></i>
</td> </td>
<td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></i>
</td>
</tr> </tr>
<tr> <tr>
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider supports some kind of ALIAS, ANAME or flattened CNAME record type">ALIAS</th> <th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider supports some kind of ALIAS, ANAME or flattened CNAME record type">ALIAS</th>
@ -517,6 +527,9 @@
<td class="danger" data-toggle="tooltip" data-container="body" data-placement="top" title="R53 does not provide a generic ALIAS functionality. Use R53_ALIAS instead."> <td class="danger" data-toggle="tooltip" data-container="body" data-placement="top" title="R53 does not provide a generic ALIAS functionality. Use R53_ALIAS instead.">
<i class="fa has-tooltip fa-times text-danger" aria-hidden="true"></i> <i class="fa has-tooltip fa-times text-danger" aria-hidden="true"></i>
</td> </td>
<td class="danger">
<i class="fa fa-times text-danger" 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="danger"> <td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></i> <i class="fa fa-times text-danger" aria-hidden="true"></i>
@ -587,6 +600,9 @@
<i class="fa fa-check text-success" aria-hidden="true"></i> <i class="fa fa-check text-success" aria-hidden="true"></i>
</td> </td>
<td><i class="fa fa-minus dim"></i></td> <td><i class="fa fa-minus dim"></i></td>
<td class="info" data-toggle="tooltip" data-container="body" data-placement="top" title="Supported by RWTH but not implemented yet.">
<i class="fa fa-circle-o text-info" 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="danger"> <td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></i> <i class="fa fa-times text-danger" aria-hidden="true"></i>
@ -695,6 +711,9 @@
<td class="success"> <td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i> <i class="fa fa-check text-success" aria-hidden="true"></i>
</td> </td>
<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><i class="fa fa-minus dim"></i></td>
<td class="success"> <td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i> <i class="fa fa-check text-success" aria-hidden="true"></i>
@ -805,6 +824,9 @@
<td class="success"> <td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i> <i class="fa fa-check text-success" aria-hidden="true"></i>
</td> </td>
<td class="success" data-toggle="tooltip" data-container="body" data-placement="top" title="PTR records with empty targets are not supported">
<i class="fa has-tooltip 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><i class="fa fa-minus dim"></i></td> <td><i class="fa fa-minus dim"></i></td>
<td class="danger"> <td class="danger">
@ -879,6 +901,9 @@
<i class="fa fa-check text-success" aria-hidden="true"></i> <i class="fa fa-check text-success" aria-hidden="true"></i>
</td> </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>
<td><i class="fa fa-minus dim"></i></td> <td><i class="fa fa-minus dim"></i></td>
<td class="success"> <td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i> <i class="fa fa-check text-success" aria-hidden="true"></i>
@ -934,6 +959,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><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>
</tr> </tr>
<tr> <tr>
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Driver has explicitly implemented SRV record management">SRV</th> <th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Driver has explicitly implemented SRV record management">SRV</th>
@ -1041,6 +1067,9 @@
<td class="success"> <td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i> <i class="fa fa-check text-success" aria-hidden="true"></i>
</td> </td>
<td class="success" data-toggle="tooltip" data-container="body" data-placement="top" title="SRV records with empty targets are not supported.">
<i class="fa has-tooltip fa-check text-success" aria-hidden="true"></i>
</td>
<td class="success"> <td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i> <i class="fa fa-check text-success" aria-hidden="true"></i>
</td> </td>
@ -1131,6 +1160,9 @@
<i class="fa fa-check text-success" aria-hidden="true"></i> <i class="fa fa-check text-success" aria-hidden="true"></i>
</td> </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>
<td><i class="fa fa-minus dim"></i></td> <td><i class="fa fa-minus dim"></i></td>
<td class="success"> <td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i> <i class="fa fa-check text-success" aria-hidden="true"></i>
@ -1227,6 +1259,9 @@
<i class="fa fa-check text-success" aria-hidden="true"></i> <i class="fa fa-check text-success" aria-hidden="true"></i>
</td> </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>
<td><i class="fa fa-minus dim"></i></td> <td><i class="fa fa-minus dim"></i></td>
<td class="success"> <td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i> <i class="fa fa-check text-success" aria-hidden="true"></i>
@ -1328,6 +1363,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><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>
</tr> </tr>
<tr> <tr>
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider supports Azure DNS limited ALIAS">AZURE_ALIAS</th> <th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider supports Azure DNS limited ALIAS">AZURE_ALIAS</th>
@ -1375,6 +1411,9 @@
<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><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>
<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><i class="fa fa-minus dim"></i></td> <td><i class="fa fa-minus dim"></i></td>
@ -1451,6 +1490,9 @@
<i class="fa fa-check text-success" aria-hidden="true"></i> <i class="fa fa-check text-success" aria-hidden="true"></i>
</td> </td>
<td><i class="fa fa-minus dim"></i></td> <td><i class="fa fa-minus dim"></i></td>
<td class="info" data-toggle="tooltip" data-container="body" data-placement="top" title="DS records are only supported at the apex and require a different API call that hasn&#39;t been implemented yet.">
<i class="fa fa-circle-o text-info" 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="danger"> <td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></i> <i class="fa fa-times text-danger" aria-hidden="true"></i>
@ -1605,6 +1647,9 @@
<td class="success"> <td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i> <i class="fa fa-check text-success" aria-hidden="true"></i>
</td> </td>
<td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></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><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>
@ -1731,6 +1776,9 @@
<td class="danger"> <td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></i> <i class="fa fa-times text-danger" aria-hidden="true"></i>
</td> </td>
<td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></i>
</td>
<td class="success"> <td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i> <i class="fa fa-check text-success" aria-hidden="true"></i>
</td> </td>
@ -1860,6 +1908,9 @@
<td class="success"> <td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i> <i class="fa fa-check text-success" aria-hidden="true"></i>
</td> </td>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
</tr> </tr>
<tr> <tr>
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="indicates the dnscontrol get-zones subcommand is implemented.">get-zones</th> <th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="indicates the dnscontrol get-zones subcommand is implemented.">get-zones</th>
@ -1969,6 +2020,9 @@
<td class="success"> <td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i> <i class="fa fa-check text-success" aria-hidden="true"></i>
</td> </td>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
<td class="info"> <td class="info">
<i class="fa fa-circle-o text-info" aria-hidden="true"></i> <i class="fa fa-circle-o text-info" aria-hidden="true"></i>
</td> </td>

43
docs/_providers/rwth.md Normal file
View File

@ -0,0 +1,43 @@
---
name: RWTH
title: RWTH DNS-Admin Provider
layout: default
jsId: RWTH
---
# RWTH DNS-Admin Provider
## Configuration
To use this provider, add an entry to `creds.json` with `TYPE` set to `RWTH`
along with your [API Token](https://noc-portal.rz.rwth-aachen.de/dns-admin/en/api_tokens).
Example:
```json
{
"rwth": {
"TYPE": "RWTH",
"api_key": "bQGz0DOi0AkTzG...="
}
}
```
## Metadata
This provider does not recognize any special metadata fields unique to it.
## Usage
An example `dnsconfig.js` configuration:
```js
var REG_NONE = NewRegistrar("none");
var DSP_RWTH = NewDnsProvider("rwth");
D("example.rwth-aachen.de", REG_NONE, DnsProvider(DSP_RWTH),
A("test", "1.2.3.4")
);
```
## Caveats
The default TTL is not automatically fetched, as the API does not provide such an endpoint.
The RWTH deploys zones every 15 minutes, so it might take some time for changes to take effect.

View File

@ -102,6 +102,7 @@ Providers in this category and their maintainers are:
* `OVH` @masterzen * `OVH` @masterzen
* `PACKETFRAME` @hamptonmoore * `PACKETFRAME` @hamptonmoore
* `POWERDNS` @jpbede * `POWERDNS` @jpbede
* `RWTH` @MisterErwin
* `ROUTE53` @tresni * `ROUTE53` @tresni
* `SOFTLAYER`@jamielennox * `SOFTLAYER`@jamielennox
* `TRANSIP` @blackshadev * `TRANSIP` @blackshadev

View File

@ -148,12 +148,12 @@ func (z *ZoneGenData) generateZoneFileHelper(w io.Writer) error {
} }
fmt.Fprintf(w, "%s%s%s\n", fmt.Fprintf(w, "%s%s%s\n",
prefix, formatLine([]int{10, 5, 2, 5, 0}, []string{name, ttl, "IN", typeStr, target}), comment) prefix, FormatLine([]int{10, 5, 2, 5, 0}, []string{name, ttl, "IN", typeStr, target}), comment)
} }
return nil return nil
} }
func formatLine(lengths []int, fields []string) string { func FormatLine(lengths []int, fields []string) string {
c := 0 c := 0
result := "" result := ""
for i, length := range lengths { for i, length := range lengths {

View File

@ -41,6 +41,7 @@ import (
_ "github.com/StackExchange/dnscontrol/v3/providers/packetframe" _ "github.com/StackExchange/dnscontrol/v3/providers/packetframe"
_ "github.com/StackExchange/dnscontrol/v3/providers/powerdns" _ "github.com/StackExchange/dnscontrol/v3/providers/powerdns"
_ "github.com/StackExchange/dnscontrol/v3/providers/route53" _ "github.com/StackExchange/dnscontrol/v3/providers/route53"
_ "github.com/StackExchange/dnscontrol/v3/providers/rwth"
_ "github.com/StackExchange/dnscontrol/v3/providers/softlayer" _ "github.com/StackExchange/dnscontrol/v3/providers/softlayer"
_ "github.com/StackExchange/dnscontrol/v3/providers/transip" _ "github.com/StackExchange/dnscontrol/v3/providers/transip"
_ "github.com/StackExchange/dnscontrol/v3/providers/vultr" _ "github.com/StackExchange/dnscontrol/v3/providers/vultr"

200
providers/rwth/api.go Normal file
View File

@ -0,0 +1,200 @@
package rwth
// The documentation is hosted at https://noc-portal.rz.rwth-aachen.de/dns-admin/en/api_tokens and
// https://blog.rwth-aachen.de/itc/2022/07/13/api-im-dns-admin/
import (
"encoding/json"
"fmt"
"github.com/StackExchange/dnscontrol/v3/models"
"github.com/StackExchange/dnscontrol/v3/pkg/printer"
"github.com/miekg/dns"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"strings"
"time"
)
const (
baseURL = "https://noc-portal.rz.rwth-aachen.de/dns-admin/api/v1"
)
type RecordReply struct {
ID int `json:"id"`
ZoneID int `json:"zone_id"`
Type string `json:"type"`
Content string `json:"content"`
Status string `json:"status"`
UpdatedAt time.Time `json:"updated_at"`
Editable bool `json:"editable"`
rec dns.RR // Store miekg/dns
}
type zone struct {
ID int `json:"id"`
ZoneName string `json:"zone_name"`
Status string `json:"status"`
UpdatedAt time.Time `json:"updated_at"`
LastDeploy time.Time `json:"last_deploy"`
Dnssec struct {
ZoneSigningKey struct {
CreatedAt time.Time `json:"created_at"`
} `json:"zone_signing_key"`
KeySigningKey struct {
CreatedAt time.Time `json:"created_at"`
} `json:"key_signing_key"`
} `json:"dnssec"`
}
func checkIsLockedSystemAPIRecord(record RecordReply) error {
if record.Type == "soa_record" {
// The upload of a BIND zone file can change the SOA record.
// Implementing this edge case this is too complex for now.
return fmt.Errorf("SOA records are locked in RWTH zones. They are hence not available for updating")
}
return nil
}
func checkIsLockedSystemRecord(record *models.RecordConfig) error {
if record.Type == "SOA" {
// The upload of a BIND zone file can change the SOA record.
// Implementing this edge case this is too complex for now.
return fmt.Errorf("SOA records are locked in RWTH zones. They are hence not available for updating")
}
return nil
}
func (api *rwthProvider) createRecord(domain string, record *models.RecordConfig) error {
if err := checkIsLockedSystemRecord(record); err != nil {
return err
}
req := url.Values{}
req.Set("record_content", api.printRecConfig(*record))
return api.request("/create_record", "POST", req, nil)
}
func (api *rwthProvider) destroyRecord(record RecordReply) error {
if err := checkIsLockedSystemAPIRecord(record); err != nil {
return err
}
req := url.Values{}
req.Set("record_id", strconv.Itoa(record.ID))
return api.request("/destroy_record", "DELETE", req, nil)
}
func (api *rwthProvider) updateRecord(id int, record models.RecordConfig) error {
if err := checkIsLockedSystemRecord(&record); err != nil {
return err
}
req := url.Values{}
req.Set("record_id", strconv.Itoa(id))
req.Set("record_content", api.printRecConfig(record))
return api.request("/update_record", "POST", req, nil)
}
func (api *rwthProvider) getAllRecords(domain string) ([]models.RecordConfig, error) {
zone, err := api.getZone(domain)
if err != nil {
return nil, err
}
records := make([]models.RecordConfig, 0)
response := []RecordReply{}
request := url.Values{}
request.Set("zone_id", strconv.Itoa(zone.ID))
if err := api.request("/list_records", "GET", request, &response); err != nil {
return nil, fmt.Errorf("failed fetching zone records for %q: %w", domain, err)
}
for _, apiRecord := range response {
if checkIsLockedSystemAPIRecord(apiRecord) != nil {
continue
}
dnsRec, err := NewRR(apiRecord.Content) // Parse content as DNS record
if err != nil {
return nil, err
}
recConfig, err := models.RRtoRC(dnsRec, domain) // and make it a RC
if err != nil {
return nil, err
}
recConfig.Original = apiRecord // but keep our ApiRecord as the original
records = append(records, recConfig)
}
return records, nil
}
func (api *rwthProvider) getAllZones() error {
if api.zones != nil {
return nil
}
zones := map[string]zone{}
response := &[]zone{}
if err := api.request("/list_zones", "GET", url.Values{}, response); err != nil {
return fmt.Errorf("failed fetching zones: %w", err)
}
for _, zone := range *response {
zones[zone.ZoneName] = zone
}
api.zones = zones
return nil
}
func (api *rwthProvider) getZone(name string) (*zone, error) {
if err := api.getAllZones(); err != nil {
return nil, err
}
zone, ok := api.zones[name]
if !ok {
return nil, fmt.Errorf("%q is not a zone in this RWTH account", name)
}
return &zone, nil
}
// Deploy the zone
func (api *rwthProvider) deployZone(domain string) error {
zone, err := api.getZone(domain)
if err != nil {
return err
}
req := url.Values{}
req.Set("zone_id", strconv.Itoa(zone.ID))
return api.request("/deploy_zone", "POST", req, nil)
}
// Send a request
func (api *rwthProvider) request(endpoint string, method string, request url.Values, target interface{}) error {
requestBody := strings.NewReader(request.Encode())
req, err := http.NewRequest(method, baseURL+endpoint, requestBody)
if err != nil {
return err
}
req.Header.Add("PRIVATE-TOKEN", api.apiToken)
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
cleanupResponseBody := func() {
err := resp.Body.Close()
if err != nil {
printer.Printf("failed closing response body: %q\n", err)
}
}
defer cleanupResponseBody()
if resp.StatusCode != http.StatusOK {
data, _ := ioutil.ReadAll(resp.Body)
printer.Printf(string(data))
return fmt.Errorf("bad status code from RWTH: %d not 200", resp.StatusCode)
}
if target == nil {
return nil
}
decoder := json.NewDecoder(resp.Body)
return decoder.Decode(target)
}

View File

@ -0,0 +1,25 @@
package rwth
import (
"github.com/StackExchange/dnscontrol/v3/models"
"github.com/StackExchange/dnscontrol/v3/pkg/recordaudit"
)
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
if err := recordaudit.TxtNoMultipleStrings(records); err != nil {
return err
}
if err := recordaudit.TxtNoTrailingSpace(records); err != nil {
return err
}
if err := recordaudit.TxtNotEmpty(records); err != nil {
return err
}
return nil
}

55
providers/rwth/convert.go Normal file
View File

@ -0,0 +1,55 @@
package rwth
import (
"fmt"
"github.com/StackExchange/dnscontrol/v3/models"
"github.com/StackExchange/dnscontrol/v3/pkg/prettyzone"
"github.com/miekg/dns"
"io"
"strings"
)
// Print the generateZoneFileHelper
func (api *rwthProvider) printRecConfig(rr models.RecordConfig) string {
// Similar to prettyzone
// Fake types are commented out.
prefix := ""
_, ok := dns.StringToType[rr.Type]
if !ok {
prefix = ";"
}
// ttl
ttl := ""
if rr.TTL != 172800 && rr.TTL != 0 {
ttl = fmt.Sprint(rr.TTL)
}
// type
typeStr := rr.Type
// the remaining line
target := rr.GetTargetCombined()
// comment
comment := ";"
return fmt.Sprintf("%s%s%s\n",
prefix, prettyzone.FormatLine([]int{10, 5, 2, 5, 0}, []string{rr.NameFQDN, ttl, "IN", typeStr, target}), comment)
}
// NewRR returns custom dns.NewRR with RWTH default TTL
func NewRR(s string) (dns.RR, error) {
if len(s) > 0 && s[len(s)-1] != '\n' { // We need a closing newline
return ReadRR(strings.NewReader(s + "\n"))
}
return ReadRR(strings.NewReader(s))
}
func ReadRR(r io.Reader) (dns.RR, error) {
zp := dns.NewZoneParser(r, ".", "")
zp.SetDefaultTTL(172800)
zp.SetIncludeAllowed(true)
rr, _ := zp.Next()
return rr, zp.Err()
}

91
providers/rwth/dns.go Normal file
View File

@ -0,0 +1,91 @@
package rwth
import (
"fmt"
"github.com/StackExchange/dnscontrol/v3/models"
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
)
var RWTHDefaultNs = []string{"dns-1.dfn.de", "dns-2.dfn.de", "zs1.rz.rwth-aachen.de", "zs2.rz.rwth-aachen.de"}
// GetZoneRecords gets the records of a zone and returns them in RecordConfig format.
func (api *rwthProvider) GetZoneRecords(domain string) (models.Records, error) {
records, err := api.getAllRecords(domain)
if err != nil {
return nil, err
}
foundRecords := models.Records{}
for i := range records {
foundRecords = append(foundRecords, &records[i])
}
return foundRecords, nil
}
// GetNameservers returns the default nameservers for RWTH.
func (api *rwthProvider) GetNameservers(domain string) ([]*models.Nameserver, error) {
return models.ToNameservers(RWTHDefaultNs)
}
func (api *rwthProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
dc, err := dc.Copy()
if err != nil {
return nil, err
}
err = dc.Punycode()
if err != nil {
return nil, err
}
domain := dc.Name
// Get existing records
existingRecords, err := api.GetZoneRecords(domain)
if err != nil {
return nil, err
}
// Normalize
models.PostProcessRecords(existingRecords)
txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
differ := diff.New(dc)
_, create, del, modify, err := differ.IncrementalDiff(existingRecords)
if err != nil {
return nil, err
}
var corrections []*models.Correction
for _, d := range create {
des := d.Desired
corrections = append(corrections, &models.Correction{
Msg: d.String(),
F: func() error { return api.createRecord(dc.Name, des) },
})
}
for _, d := range del {
existingRecord := d.Existing.Original.(RecordReply)
corrections = append(corrections, &models.Correction{
Msg: d.String(),
F: func() error { return api.destroyRecord(existingRecord) },
})
}
for _, d := range modify {
rec := d.Desired
existingID := d.Existing.Original.(RecordReply).ID
corrections = append(corrections, &models.Correction{
Msg: d.String(),
F: func() error { return api.updateRecord(existingID, *rec) },
})
}
// And deploy if any corrections were applied
if len(corrections) > 0 {
corrections = append(corrections, &models.Correction{
Msg: fmt.Sprintf("Deploy zone %s", domain),
F: func() error { return api.deployZone(domain) },
})
}
return corrections, nil
}

View File

@ -0,0 +1,13 @@
package rwth
// ListZones lists the zones on this account.
func (api *rwthProvider) ListZones() ([]string, error) {
if err := api.getAllZones(); err != nil {
return nil, err
}
var zones []string
for i := range api.zones {
zones = append(zones, i)
}
return zones, nil
}

View File

@ -0,0 +1,3 @@
package rwth
// No registrar functionality

View File

@ -0,0 +1,49 @@
package rwth
import (
"encoding/json"
"fmt"
"github.com/StackExchange/dnscontrol/v3/providers"
)
type rwthProvider struct {
apiToken string
zones map[string]zone
}
// features is used to let dnscontrol know which features are supported by the RWTH DNS Admin.
var features = providers.DocumentationNotes{
providers.CanAutoDNSSEC: providers.Unimplemented("Supported by RWTH but not implemented yet."),
providers.CanGetZones: providers.Can(),
providers.CanUseAlias: providers.Cannot(),
providers.CanUseAzureAlias: providers.Cannot(),
providers.CanUseCAA: providers.Can(),
providers.CanUseDS: providers.Unimplemented("DS records are only supported at the apex and require a different API call that hasn't been implemented yet."),
providers.CanUseNAPTR: providers.Cannot(),
providers.CanUsePTR: providers.Can("PTR records with empty targets are not supported"),
providers.CanUseSRV: providers.Can("SRV records with empty targets are not supported."),
providers.CanUseSSHFP: providers.Can(),
providers.CanUseTLSA: providers.Cannot(),
providers.DocCreateDomains: providers.Cannot(),
providers.DocDualHost: providers.Cannot(),
providers.DocOfficiallySupported: providers.Cannot(),
}
// init registers the registrar and the domain service provider with dnscontrol.
func init() {
fns := providers.DspFuncs{
Initializer: New,
RecordAuditor: AuditRecords,
}
providers.RegisterDomainServiceProviderType("RWTH", fns, features)
}
func New(settings map[string]string, _ json.RawMessage) (providers.DNSServiceProvider, error) {
if settings["api_token"] == "" {
return nil, fmt.Errorf("missing RWTH api_token")
}
api := &rwthProvider{apiToken: settings["api_token"]}
return api, nil
}