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/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
|
||||||
|
@ -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
|
||||||
|
@ -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'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
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
|
* `OVH` @masterzen
|
||||||
* `PACKETFRAME` @hamptonmoore
|
* `PACKETFRAME` @hamptonmoore
|
||||||
* `POWERDNS` @jpbede
|
* `POWERDNS` @jpbede
|
||||||
|
* `RWTH` @MisterErwin
|
||||||
* `ROUTE53` @tresni
|
* `ROUTE53` @tresni
|
||||||
* `SOFTLAYER`@jamielennox
|
* `SOFTLAYER`@jamielennox
|
||||||
* `TRANSIP` @blackshadev
|
* `TRANSIP` @blackshadev
|
||||||
|
@ -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 {
|
||||||
|
@ -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
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