mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
PORKBUN: New provider (#1819)
Co-authored-by: Tom Limoncelli <tlimoncelli@stackoverflow.com>
This commit is contained in:
1
OWNERS
1
OWNERS
@@ -38,4 +38,5 @@ providers/vultr @pgaskin
|
|||||||
providers/ovh @masterzen
|
providers/ovh @masterzen
|
||||||
providers/powerdns @jpbede
|
providers/powerdns @jpbede
|
||||||
providers/packetframe @hamptonmoore
|
providers/packetframe @hamptonmoore
|
||||||
|
providers/porkbun @imlonghao
|
||||||
providers/transip @blackshadev
|
providers/transip @blackshadev
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ Currently supported DNS providers:
|
|||||||
- OctoDNS
|
- OctoDNS
|
||||||
- Oracle Cloud
|
- Oracle Cloud
|
||||||
- Packetframe
|
- Packetframe
|
||||||
|
- Porkbun
|
||||||
- PowerDNS
|
- PowerDNS
|
||||||
- RWTH DNS-Admin
|
- RWTH DNS-Admin
|
||||||
- SoftLayer
|
- SoftLayer
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
<th class="rotate"><div><span>ORACLE</span></div></th>
|
<th class="rotate"><div><span>ORACLE</span></div></th>
|
||||||
<th class="rotate"><div><span>OVH</span></div></th>
|
<th class="rotate"><div><span>OVH</span></div></th>
|
||||||
<th class="rotate"><div><span>PACKETFRAME</span></div></th>
|
<th class="rotate"><div><span>PACKETFRAME</span></div></th>
|
||||||
|
<th class="rotate"><div><span>PORKBUN</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>RWTH</span></div></th>
|
||||||
@@ -163,6 +164,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>
|
||||||
@@ -307,6 +311,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>
|
||||||
@@ -421,6 +428,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>
|
||||||
@@ -521,6 +531,9 @@
|
|||||||
<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><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 class="success" data-toggle="tooltip" data-container="body" data-placement="top" title="Needs to be enabled in PowerDNS first">
|
<td class="success" data-toggle="tooltip" data-container="body" data-placement="top" title="Needs to be enabled in PowerDNS first">
|
||||||
<a href="https://doc.powerdns.com/authoritative/guides/alias.html"><i class="fa has-tooltip fa-check text-success" aria-hidden="true"></i></a>
|
<a href="https://doc.powerdns.com/authoritative/guides/alias.html"><i class="fa has-tooltip fa-check text-success" aria-hidden="true"></i></a>
|
||||||
</td>
|
</td>
|
||||||
@@ -598,6 +611,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 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>
|
||||||
@@ -707,6 +723,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">
|
||||||
|
<i class="fa fa-circle-o text-info" 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>
|
||||||
@@ -820,6 +839,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 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>
|
||||||
@@ -901,6 +923,9 @@
|
|||||||
</td>
|
</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 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>
|
||||||
@@ -958,6 +983,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>
|
||||||
@@ -1071,6 +1099,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="success" data-toggle="tooltip" data-container="body" data-placement="top" title="SRV records with empty targets are not supported.">
|
<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>
|
<i class="fa has-tooltip fa-check text-success" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
@@ -1162,6 +1193,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 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>
|
||||||
@@ -1266,6 +1300,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="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>
|
||||||
@@ -1322,6 +1359,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 Route 53 limited ALIAS">R53_ALIAS</th>
|
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider supports Route 53 limited ALIAS">R53_ALIAS</th>
|
||||||
@@ -1366,6 +1404,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>
|
||||||
<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>
|
||||||
@@ -1420,6 +1459,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>
|
||||||
<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>
|
||||||
@@ -1497,6 +1537,9 @@
|
|||||||
</td>
|
</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 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>
|
||||||
@@ -1556,6 +1599,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="This provider is recommended for use in 'dual hosting' scenarios. Usually this means the provider allows full control over the apex NS records">dual host</th>
|
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="This provider is recommended for use in 'dual hosting' scenarios. Usually this means the provider allows full control over the apex NS records">dual host</th>
|
||||||
@@ -1653,6 +1697,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>
|
||||||
@@ -1776,6 +1823,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>
|
||||||
@@ -1923,6 +1973,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>
|
||||||
@@ -2035,6 +2088,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>
|
||||||
|
|||||||
41
docs/_providers/porkbun.md
Normal file
41
docs/_providers/porkbun.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
name: Porkbun
|
||||||
|
title: Porkbun Provider
|
||||||
|
layout: default
|
||||||
|
jsId: PORKBUN
|
||||||
|
---
|
||||||
|
# Porkbun Provider
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
To use this provider, add an entry to `creds.json` with `TYPE` set to `PORKBUN`
|
||||||
|
along with your `api_key` and `secret_key`. More info about authentication can be found in [Getting started with the Porkbun API](https://kb.porkbun.com/article/190-getting-started-with-the-porkbun-api).
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"porkbun": {
|
||||||
|
"TYPE": "PORKBUN",
|
||||||
|
"api_key": "your-porkbun-api-key",
|
||||||
|
"secret_key": "your-porkbun-secret-key",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
|
||||||
|
This provider does not recognize any special metadata fields unique to Porkbun.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
An example `dnsconfig.js` configuration:
|
||||||
|
|
||||||
|
```js
|
||||||
|
var REG_NONE = NewRegistrar("none");
|
||||||
|
var DSP_PORKBUN = NewDnsProvider("porkbun");
|
||||||
|
|
||||||
|
D("example.tld", REG_NONE, DnsProvider(DSP_PORKBUN),
|
||||||
|
A("test", "1.2.3.4")
|
||||||
|
);
|
||||||
|
```
|
||||||
@@ -188,6 +188,11 @@
|
|||||||
"token": "$PACKETFRAME_TOKEN",
|
"token": "$PACKETFRAME_TOKEN",
|
||||||
"domain": "$PACKETFRAME_DOMAIN"
|
"domain": "$PACKETFRAME_DOMAIN"
|
||||||
},
|
},
|
||||||
|
"PORKBUN": {
|
||||||
|
"api_key": "$PORKBUN_API_KEY",
|
||||||
|
"secret_key": "$PORKBUN_SECRET_KEY",
|
||||||
|
"domain": "$PORKBUN_DOMAIN"
|
||||||
|
},
|
||||||
"ROUTE53": {
|
"ROUTE53": {
|
||||||
"KeyId": "$ROUTE53_KEY_ID",
|
"KeyId": "$ROUTE53_KEY_ID",
|
||||||
"SecretKey": "$ROUTE53_KEY",
|
"SecretKey": "$ROUTE53_KEY",
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import (
|
|||||||
_ "github.com/StackExchange/dnscontrol/v3/providers/oracle"
|
_ "github.com/StackExchange/dnscontrol/v3/providers/oracle"
|
||||||
_ "github.com/StackExchange/dnscontrol/v3/providers/ovh"
|
_ "github.com/StackExchange/dnscontrol/v3/providers/ovh"
|
||||||
_ "github.com/StackExchange/dnscontrol/v3/providers/packetframe"
|
_ "github.com/StackExchange/dnscontrol/v3/providers/packetframe"
|
||||||
|
_ "github.com/StackExchange/dnscontrol/v3/providers/porkbun"
|
||||||
_ "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/rwth"
|
||||||
|
|||||||
123
providers/porkbun/api.go
Normal file
123
providers/porkbun/api.go
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
package porkbun
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
baseURL = "https://porkbun.com/api/json/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type porkbunProvider struct {
|
||||||
|
apiKey string
|
||||||
|
secretKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
type requestParams map[string]string
|
||||||
|
|
||||||
|
type errorResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type domainRecord struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
TTL string `json:"ttl"`
|
||||||
|
Prio string `json:"prio"`
|
||||||
|
Notes string `json:"notes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type recordResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Records []domainRecord `json:"records"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *porkbunProvider) post(endpoint string, params requestParams) ([]byte, error) {
|
||||||
|
params["apikey"] = c.apiKey
|
||||||
|
params["secretapikey"] = c.secretKey
|
||||||
|
|
||||||
|
personJSON, err := json.Marshal(params)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
req, _ := http.NewRequest("POST", baseURL+endpoint, bytes.NewBuffer(personJSON))
|
||||||
|
|
||||||
|
// If request sending too fast, the server will fail with the following error:
|
||||||
|
// porkbun API error: Create error: We were unable to create the DNS record.
|
||||||
|
time.Sleep(300 * time.Millisecond)
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyString, _ := io.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
// Got error from API ?
|
||||||
|
var errResp errorResponse
|
||||||
|
err = json.Unmarshal(bodyString, &errResp)
|
||||||
|
if err == nil {
|
||||||
|
if errResp.Status == "ERROR" {
|
||||||
|
return bodyString, fmt.Errorf("porkbun API error: %s URL:%s%s ", errResp.Message, req.Host, req.URL.RequestURI())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bodyString, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *porkbunProvider) ping() error {
|
||||||
|
params := requestParams{}
|
||||||
|
_, err := c.post("/ping", params)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *porkbunProvider) createRecord(domain string, rec requestParams) error {
|
||||||
|
if _, err := c.post("/dns/create/"+domain, rec); err != nil {
|
||||||
|
return fmt.Errorf("failed create record (porkbun): %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *porkbunProvider) deleteRecord(domain string, recordID string) error {
|
||||||
|
params := requestParams{}
|
||||||
|
if _, err := c.post(fmt.Sprintf("/dns/delete/%s/%s", domain, recordID), params); err != nil {
|
||||||
|
return fmt.Errorf("failed delete record (porkbun): %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *porkbunProvider) modifyRecord(domain string, recordID string, rec requestParams) error {
|
||||||
|
if _, err := c.post(fmt.Sprintf("/dns/edit/%s/%s", domain, recordID), rec); err != nil {
|
||||||
|
return fmt.Errorf("failed update (porkbun): %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *porkbunProvider) getRecords(domain string) ([]domainRecord, error) {
|
||||||
|
params := requestParams{}
|
||||||
|
var bodyString, err = c.post("/dns/retrieve/"+domain, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed fetching record list from porkbun: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var dr recordResponse
|
||||||
|
json.Unmarshal(bodyString, &dr)
|
||||||
|
|
||||||
|
var records []domainRecord
|
||||||
|
for _, rec := range dr.Records {
|
||||||
|
if rec.Name == domain && rec.Type == "NS" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
records = append(records, rec)
|
||||||
|
}
|
||||||
|
return records, nil
|
||||||
|
}
|
||||||
19
providers/porkbun/auditrecords.go
Normal file
19
providers/porkbun/auditrecords.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package porkbun
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/pkg/rejectif"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditRecords returns a list of errors corresponding to the records
|
||||||
|
// that aren't supported by this provider. If all records are
|
||||||
|
// supported, an empty list is returned.
|
||||||
|
func AuditRecords(records []*models.RecordConfig) []error {
|
||||||
|
a := rejectif.Auditor{}
|
||||||
|
|
||||||
|
a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2022-11-19
|
||||||
|
|
||||||
|
a.Add("TXT", rejectif.TxtHasTrailingSpace) // Last verified 2022-11-19
|
||||||
|
|
||||||
|
return a.Audit(records)
|
||||||
|
}
|
||||||
281
providers/porkbun/porkbunProvider.go
Normal file
281
providers/porkbun/porkbunProvider.go
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
package porkbun
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/pkg/printer"
|
||||||
|
"github.com/StackExchange/dnscontrol/v3/providers"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
minimumTTL = 600
|
||||||
|
)
|
||||||
|
|
||||||
|
// https://kb.porkbun.com/article/63-how-to-switch-to-porkbuns-nameservers
|
||||||
|
var defaultNS = []string{
|
||||||
|
"curitiba.ns.porkbun.com",
|
||||||
|
"fortaleza.ns.porkbun.com",
|
||||||
|
"maceio.ns.porkbun.com",
|
||||||
|
"salvador.ns.porkbun.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPorkbun creates the provider.
|
||||||
|
func NewPorkbun(m map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
|
||||||
|
c := &porkbunProvider{}
|
||||||
|
|
||||||
|
c.apiKey, c.secretKey = m["api_key"], m["secret_key"]
|
||||||
|
|
||||||
|
if c.apiKey == "" || c.secretKey == "" {
|
||||||
|
return nil, fmt.Errorf("missing porkbun api_key or secret_key")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate authentication
|
||||||
|
if err := c.ping(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var features = providers.DocumentationNotes{
|
||||||
|
providers.CanAutoDNSSEC: providers.Cannot(),
|
||||||
|
providers.CanGetZones: providers.Can(),
|
||||||
|
providers.CanUseAlias: providers.Can(),
|
||||||
|
providers.CanUseCAA: providers.Unimplemented(), // CAA record for base domain is pinning to a fixed set once configure
|
||||||
|
providers.CanUseDS: providers.Cannot(),
|
||||||
|
providers.CanUseDSForChildren: providers.Cannot(),
|
||||||
|
providers.CanUseNAPTR: providers.Cannot(),
|
||||||
|
providers.CanUsePTR: providers.Cannot(),
|
||||||
|
providers.CanUseSOA: providers.Cannot(),
|
||||||
|
providers.CanUseSRV: providers.Can(),
|
||||||
|
providers.CanUseSSHFP: providers.Cannot(),
|
||||||
|
providers.CanUseTLSA: providers.Can(),
|
||||||
|
providers.DocCreateDomains: providers.Cannot(),
|
||||||
|
providers.DocDualHost: providers.Cannot(),
|
||||||
|
providers.DocOfficiallySupported: providers.Cannot(),
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
fns := providers.DspFuncs{
|
||||||
|
Initializer: NewPorkbun,
|
||||||
|
RecordAuditor: AuditRecords,
|
||||||
|
}
|
||||||
|
providers.RegisterDomainServiceProviderType("PORKBUN", fns, features)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNameservers returns the nameservers for a domain.
|
||||||
|
func (c *porkbunProvider) GetNameservers(domain string) ([]*models.Nameserver, error) {
|
||||||
|
return models.ToNameservers(defaultNS)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDomainCorrections returns the corrections for a domain.
|
||||||
|
func (c *porkbunProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||||
|
dc, err := dc.Copy()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dc.Punycode()
|
||||||
|
|
||||||
|
existingRecords, err := c.GetZoneRecords(dc.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block changes to NS records for base domain
|
||||||
|
checkNSModifications(dc)
|
||||||
|
|
||||||
|
// Normalize
|
||||||
|
models.PostProcessRecords(existingRecords)
|
||||||
|
|
||||||
|
// Make sure TTL larger than the minimum TTL
|
||||||
|
for _, record := range dc.Records {
|
||||||
|
record.TTL = fixTTL(record.TTL)
|
||||||
|
}
|
||||||
|
|
||||||
|
differ := diff.New(dc)
|
||||||
|
_, create, del, modify, err := differ.IncrementalDiff(existingRecords)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var corrections []*models.Correction
|
||||||
|
|
||||||
|
// Deletes first so changing type works etc.
|
||||||
|
for _, m := range del {
|
||||||
|
id := m.Existing.Original.(*domainRecord).ID
|
||||||
|
corr := &models.Correction{
|
||||||
|
Msg: fmt.Sprintf("%s, porkbun ID: %s", m.String(), id),
|
||||||
|
F: func() error {
|
||||||
|
return c.deleteRecord(dc.Name, id)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
corrections = append(corrections, corr)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, m := range create {
|
||||||
|
req, err := toReq(m.Desired)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
corr := &models.Correction{
|
||||||
|
Msg: m.String(),
|
||||||
|
F: func() error {
|
||||||
|
return c.createRecord(dc.Name, req)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
corrections = append(corrections, corr)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, m := range modify {
|
||||||
|
id := m.Existing.Original.(*domainRecord).ID
|
||||||
|
req, err := toReq(m.Desired)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
corr := &models.Correction{
|
||||||
|
Msg: fmt.Sprintf("%s, porkbun ID: %s: ", m.String(), id),
|
||||||
|
F: func() error {
|
||||||
|
return c.modifyRecord(dc.Name, id, req)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
corrections = append(corrections, corr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return corrections, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetZoneRecords gets the records of a zone and returns them in RecordConfig format.
|
||||||
|
func (c *porkbunProvider) GetZoneRecords(domain string) (models.Records, error) {
|
||||||
|
records, err := c.getRecords(domain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
existingRecords := make([]*models.RecordConfig, len(records))
|
||||||
|
for i := range records {
|
||||||
|
existingRecords[i] = toRc(domain, &records[i])
|
||||||
|
}
|
||||||
|
return existingRecords, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parses the porkbun format into our standard RecordConfig
|
||||||
|
func toRc(domain string, r *domainRecord) *models.RecordConfig {
|
||||||
|
ttl, _ := strconv.ParseUint(r.TTL, 10, 32)
|
||||||
|
priority, _ := strconv.ParseUint(r.Prio, 10, 16)
|
||||||
|
|
||||||
|
rc := &models.RecordConfig{
|
||||||
|
Type: r.Type,
|
||||||
|
TTL: uint32(ttl),
|
||||||
|
MxPreference: uint16(priority),
|
||||||
|
SrvPriority: uint16(priority),
|
||||||
|
Original: r,
|
||||||
|
}
|
||||||
|
rc.SetLabelFromFQDN(r.Name, domain)
|
||||||
|
|
||||||
|
switch rtype := r.Type; rtype { // #rtype_variations
|
||||||
|
case "TXT":
|
||||||
|
rc.SetTargetTXT(r.Content)
|
||||||
|
case "MX", "CNAME", "ALIAS", "NS":
|
||||||
|
if strings.HasSuffix(r.Content, ".") {
|
||||||
|
rc.SetTarget(r.Content)
|
||||||
|
} else {
|
||||||
|
rc.SetTarget(r.Content + ".")
|
||||||
|
}
|
||||||
|
case "CAA":
|
||||||
|
// 0, issue, "letsencrypt.org"
|
||||||
|
c := strings.Split(r.Content, " ")
|
||||||
|
|
||||||
|
caaFlag, _ := strconv.ParseUint(c[0], 10, 8)
|
||||||
|
rc.CaaFlag = uint8(caaFlag)
|
||||||
|
rc.CaaTag = c[1]
|
||||||
|
rc.SetTarget(strings.ReplaceAll(c[2], "\"", ""))
|
||||||
|
case "TLSA":
|
||||||
|
// 0 0 0 00000000000000000000000
|
||||||
|
c := strings.Split(r.Content, " ")
|
||||||
|
|
||||||
|
tlsaUsage, _ := strconv.ParseUint(c[0], 10, 8)
|
||||||
|
rc.TlsaUsage = uint8(tlsaUsage)
|
||||||
|
tlsaSelector, _ := strconv.ParseUint(c[1], 10, 8)
|
||||||
|
rc.TlsaSelector = uint8(tlsaSelector)
|
||||||
|
tlsaMatchingType, _ := strconv.ParseUint(c[2], 10, 8)
|
||||||
|
rc.TlsaMatchingType = uint8(tlsaMatchingType)
|
||||||
|
rc.SetTarget(c[3])
|
||||||
|
case "SRV":
|
||||||
|
// 5 5060 sip.example.com
|
||||||
|
c := strings.Split(r.Content, " ")
|
||||||
|
|
||||||
|
srvWeight, _ := strconv.ParseUint(c[0], 10, 16)
|
||||||
|
rc.SrvWeight = uint16(srvWeight)
|
||||||
|
srvPort, _ := strconv.ParseUint(c[1], 10, 16)
|
||||||
|
rc.SrvPort = uint16(srvPort)
|
||||||
|
rc.SetTarget(c[2])
|
||||||
|
default:
|
||||||
|
rc.SetTarget(r.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc
|
||||||
|
}
|
||||||
|
|
||||||
|
// toReq takes a RecordConfig and turns it into the native format used by the API.
|
||||||
|
func toReq(rc *models.RecordConfig) (requestParams, error) {
|
||||||
|
req := requestParams{
|
||||||
|
"type": rc.Type,
|
||||||
|
"name": rc.GetLabel(),
|
||||||
|
"content": rc.GetTargetField(),
|
||||||
|
"ttl": strconv.Itoa(int(rc.TTL)),
|
||||||
|
}
|
||||||
|
|
||||||
|
// porkbun doesn't use "@", it uses an empty name
|
||||||
|
if req["name"] == "@" {
|
||||||
|
req["name"] = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
switch rc.Type { // #rtype_variations
|
||||||
|
case "A", "AAAA", "NS", "ALIAS", "CNAME":
|
||||||
|
// Nothing special.
|
||||||
|
case "TXT":
|
||||||
|
req["content"] = rc.GetTargetTXTJoined()
|
||||||
|
case "MX":
|
||||||
|
req["prio"] = strconv.Itoa(int(rc.MxPreference))
|
||||||
|
case "SRV":
|
||||||
|
req["prio"] = strconv.Itoa(int(rc.SrvPriority))
|
||||||
|
req["content"] = fmt.Sprintf("%d %d %s", rc.SrvWeight, rc.SrvPort, rc.GetTargetField())
|
||||||
|
case "CAA":
|
||||||
|
req["content"] = fmt.Sprintf("%d %s \"%s\"", rc.CaaFlag, rc.CaaTag, rc.GetTargetField())
|
||||||
|
case "TLSA":
|
||||||
|
req["content"] = fmt.Sprintf("%d %d %d %s",
|
||||||
|
rc.TlsaUsage, rc.TlsaSelector, rc.TlsaMatchingType, rc.GetTargetField())
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("porkbun.toReq rtype %q unimplemented", rc.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkNSModifications(dc *models.DomainConfig) {
|
||||||
|
newList := make([]*models.RecordConfig, 0, len(dc.Records))
|
||||||
|
for _, rec := range dc.Records {
|
||||||
|
if rec.Type == "NS" && rec.GetLabelFQDN() == dc.Name {
|
||||||
|
if strings.HasSuffix(rec.GetTargetField(), ".porkbun.com") {
|
||||||
|
printer.Warnf("porkbun does not support modifying NS records on base domain. %s will not be added.\n", rec.GetTargetField())
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newList = append(newList, rec)
|
||||||
|
}
|
||||||
|
dc.Records = newList
|
||||||
|
}
|
||||||
|
|
||||||
|
func fixTTL(ttl uint32) uint32 {
|
||||||
|
if ttl > minimumTTL {
|
||||||
|
return ttl
|
||||||
|
}
|
||||||
|
return minimumTTL
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user