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:
1
OWNERS
1
OWNERS
@ -32,6 +32,7 @@ providers/ns1 @costasd
|
||||
providers/opensrs @philhug
|
||||
providers/oracle @kallsyms
|
||||
providers/route53 @tresni
|
||||
providers/rwth @mistererwin
|
||||
# providers/softlayer NEEDS VOLUNTEER
|
||||
providers/vultr @pgaskin
|
||||
providers/ovh @masterzen
|
||||
|
@ -48,6 +48,7 @@ Currently supported DNS providers:
|
||||
- Oracle Cloud
|
||||
- Packetframe
|
||||
- PowerDNS
|
||||
- RWTH DNS-Admin
|
||||
- SoftLayer
|
||||
- TransIP
|
||||
- Vultr
|
||||
|
@ -43,6 +43,7 @@
|
||||
<th class="rotate"><div><span>PACKETFRAME</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>RWTH</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>VULTR</span></div></th>
|
||||
@ -174,6 +175,9 @@
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
</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>
|
||||
@ -300,6 +304,9 @@
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
</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>
|
||||
@ -426,6 +433,9 @@
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
</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>
|
||||
@ -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.">
|
||||
<i class="fa has-tooltip fa-times text-danger" aria-hidden="true"></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 class="danger">
|
||||
<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>
|
||||
</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 class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
@ -695,6 +711,9 @@
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></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 class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
@ -805,6 +824,9 @@
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</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 class="danger">
|
||||
@ -879,6 +901,9 @@
|
||||
<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>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td class="success">
|
||||
<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>
|
||||
</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>
|
||||
@ -1041,6 +1067,9 @@
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</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">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
@ -1131,6 +1160,9 @@
|
||||
<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>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td class="success">
|
||||
<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>
|
||||
</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 class="success">
|
||||
<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>
|
||||
</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>
|
||||
@ -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 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>
|
||||
@ -1451,6 +1490,9 @@
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></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'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 class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
@ -1605,6 +1647,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><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">
|
||||
<i class="fa fa-times text-danger" 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>
|
||||
@ -1860,6 +1908,9 @@
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
</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>
|
||||
@ -1969,6 +2020,9 @@
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="info">
|
||||
<i class="fa fa-circle-o text-info" aria-hidden="true"></i>
|
||||
</td>
|
||||
|
43
docs/_providers/rwth.md
Normal file
43
docs/_providers/rwth.md
Normal 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.
|
@ -102,6 +102,7 @@ Providers in this category and their maintainers are:
|
||||
* `OVH` @masterzen
|
||||
* `PACKETFRAME` @hamptonmoore
|
||||
* `POWERDNS` @jpbede
|
||||
* `RWTH` @MisterErwin
|
||||
* `ROUTE53` @tresni
|
||||
* `SOFTLAYER`@jamielennox
|
||||
* `TRANSIP` @blackshadev
|
||||
|
@ -148,12 +148,12 @@ func (z *ZoneGenData) generateZoneFileHelper(w io.Writer) error {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func formatLine(lengths []int, fields []string) string {
|
||||
func FormatLine(lengths []int, fields []string) string {
|
||||
c := 0
|
||||
result := ""
|
||||
for i, length := range lengths {
|
||||
|
@ -41,6 +41,7 @@ import (
|
||||
_ "github.com/StackExchange/dnscontrol/v3/providers/packetframe"
|
||||
_ "github.com/StackExchange/dnscontrol/v3/providers/powerdns"
|
||||
_ "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/transip"
|
||||
_ "github.com/StackExchange/dnscontrol/v3/providers/vultr"
|
||||
|
200
providers/rwth/api.go
Normal file
200
providers/rwth/api.go
Normal 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)
|
||||
}
|
25
providers/rwth/auditrecords.go
Normal file
25
providers/rwth/auditrecords.go
Normal 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
55
providers/rwth/convert.go
Normal 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
91
providers/rwth/dns.go
Normal 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
|
||||
}
|
13
providers/rwth/listzones.go
Normal file
13
providers/rwth/listzones.go
Normal 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
|
||||
}
|
3
providers/rwth/registrar.go
Normal file
3
providers/rwth/registrar.go
Normal file
@ -0,0 +1,3 @@
|
||||
package rwth
|
||||
|
||||
// No registrar functionality
|
49
providers/rwth/rwthProvider.go
Normal file
49
providers/rwth/rwthProvider.go
Normal 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
|
||||
}
|
Reference in New Issue
Block a user