mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2025-01-11 09:59:59 +08:00
322 lines
6.9 KiB
Go
322 lines
6.9 KiB
Go
package hostingde
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
|
|
"github.com/StackExchange/dnscontrol/v4/pkg/diff"
|
|
"golang.org/x/net/idna"
|
|
)
|
|
|
|
const endpoint = "%s/api/%s/v1/json/%s"
|
|
|
|
type hostingdeProvider struct {
|
|
authToken string
|
|
ownerAccountID string
|
|
filterAccountID string
|
|
baseURL string
|
|
nameservers []string
|
|
defaultSoa soaValues
|
|
}
|
|
|
|
func (hp *hostingdeProvider) getDomainConfig(domain string) (*domainConfig, error) {
|
|
params := request{
|
|
Filter: &filter{
|
|
Field: "domainName",
|
|
Value: domain,
|
|
},
|
|
}
|
|
|
|
resp, err := hp.get("domain", "domainsFind", params)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error getting domain info: %w", err)
|
|
}
|
|
|
|
domainConf := []*domainConfig{}
|
|
if err := json.Unmarshal(resp.Data, &domainConf); err != nil {
|
|
return nil, fmt.Errorf("error parsing response: %w", err)
|
|
}
|
|
|
|
if len(domainConf) == 0 {
|
|
return nil, fmt.Errorf("could not get domain config: %s", domain)
|
|
}
|
|
|
|
return domainConf[0], nil
|
|
}
|
|
|
|
func (hp *hostingdeProvider) createZone(domain string) error {
|
|
t, err := idna.ToASCII(domain)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
records := []*record{}
|
|
for _, ns := range hp.nameservers {
|
|
records = append(records, &record{
|
|
Name: domain,
|
|
Type: "NS",
|
|
Content: ns,
|
|
TTL: 86400,
|
|
})
|
|
}
|
|
|
|
params := request{
|
|
ZoneConfig: &zoneConfig{
|
|
Name: t,
|
|
Type: "NATIVE",
|
|
},
|
|
Records: records,
|
|
}
|
|
|
|
_, err = hp.get("dns", "zoneCreate", params)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating zone: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (hp *hostingdeProvider) getNameservers(domain string) ([]string, error) {
|
|
t, err := idna.ToASCII(domain)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
domainConf, err := hp.getDomainConfig(t)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error getting domain config: %w", err)
|
|
}
|
|
|
|
nss := []string{}
|
|
for _, ns := range domainConf.Nameservers {
|
|
// Currently does not support glued IP addresses
|
|
if len(ns.IPs) > 0 {
|
|
return nil, fmt.Errorf("domain %s has glued IP addresses which are not supported", domain)
|
|
}
|
|
|
|
nss = append(nss, ns.Name)
|
|
}
|
|
|
|
return nss, nil
|
|
}
|
|
|
|
func (hp *hostingdeProvider) updateNameservers(nss []string, domain string) func() error {
|
|
return func() error {
|
|
domainConf, err := hp.getDomainConfig(domain)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
nameservers := []nameserver{}
|
|
for _, ns := range nss {
|
|
nameservers = append(nameservers, nameserver{Name: ns})
|
|
}
|
|
|
|
domainConf.Nameservers = nameservers
|
|
|
|
params := request{
|
|
Domain: domainConf,
|
|
}
|
|
|
|
if _, err := hp.get("domain", "domainUpdate", params); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (hp *hostingdeProvider) updateZone(zc *zoneConfig, options *dnsSecOptions, create, del, mod diff.Changeset) error {
|
|
toAdd := []*record{}
|
|
for _, c := range create {
|
|
r := recordToNative(c.Desired)
|
|
toAdd = append(toAdd, r)
|
|
}
|
|
|
|
toDelete := []*record{}
|
|
for _, d := range del {
|
|
r := recordToNative(d.Existing)
|
|
r.ID = d.Existing.Original.(record).ID
|
|
toDelete = append(toDelete, r)
|
|
}
|
|
|
|
toModify := []*record{}
|
|
for _, m := range mod {
|
|
r := recordToNative(m.Desired)
|
|
r.ID = m.Existing.Original.(record).ID
|
|
toModify = append(toModify, r)
|
|
}
|
|
|
|
params := request{
|
|
ZoneConfig: zc,
|
|
RecordsToAdd: toAdd,
|
|
RecordsToDelete: toDelete,
|
|
RecordsToModify: toModify,
|
|
DNSSECOptions: options,
|
|
}
|
|
|
|
_, err := hp.get("dns", "zoneUpdate", params)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (hp *hostingdeProvider) getZone(domain string) (*zone, error) {
|
|
t, err := idna.ToASCII(domain)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
params := request{
|
|
Filter: &filter{
|
|
Field: "ZoneName",
|
|
Value: t,
|
|
},
|
|
}
|
|
|
|
resp, err := hp.get("dns", "zonesFind", params)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not get zone config: %w", err)
|
|
}
|
|
|
|
zones := []*zone{}
|
|
if err := json.Unmarshal(resp.Data, &zones); err != nil {
|
|
return nil, fmt.Errorf("could not parse response: %w", err)
|
|
}
|
|
|
|
if len(zones) == 0 {
|
|
return nil, errZoneNotFound
|
|
}
|
|
|
|
return zones[0], nil
|
|
}
|
|
|
|
func (hp *hostingdeProvider) getZoneConfig(domain string) (*zoneConfig, error) {
|
|
t, err := idna.ToASCII(domain)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
params := request{
|
|
Filter: &filter{
|
|
Field: "ZoneName",
|
|
Value: t,
|
|
},
|
|
}
|
|
|
|
resp, err := hp.get("dns", "zoneConfigsFind", params)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not get zone config: %w", err)
|
|
}
|
|
|
|
zc := []*zoneConfig{}
|
|
if err := json.Unmarshal(resp.Data, &zc); err != nil {
|
|
return nil, fmt.Errorf("could not parse response: %w", err)
|
|
}
|
|
|
|
if len(zc) == 0 {
|
|
return nil, errZoneNotFound
|
|
}
|
|
|
|
return zc[0], nil
|
|
}
|
|
|
|
func (hp *hostingdeProvider) getDNSSECOptions(zoneConfigID string) (*dnsSecOptions, error) {
|
|
params := request{
|
|
Filter: &filter{
|
|
Field: "zoneConfigId",
|
|
Value: zoneConfigID,
|
|
},
|
|
}
|
|
|
|
resp, err := hp.get("dns", "dnsSecOptionsFind", params)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not get dnssec options: %w", err)
|
|
}
|
|
|
|
dnsSecOptions := []*dnsSecOptions{}
|
|
if err := json.Unmarshal(resp.Data, &dnsSecOptions); err != nil {
|
|
return nil, fmt.Errorf("could not parse response: %w", err)
|
|
}
|
|
|
|
if len(dnsSecOptions) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
return dnsSecOptions[0], nil
|
|
}
|
|
|
|
func (hp *hostingdeProvider) dnsSecKeyModify(domain string, add []dnsSecEntry, remove []dnsSecEntry) error {
|
|
params := request{
|
|
DomainName: domain,
|
|
Add: add,
|
|
Remove: remove,
|
|
}
|
|
|
|
_, err := hp.get("domain", "dnsSecKeyModify", params)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (hp *hostingdeProvider) get(service, method string, params request) (*responseData, error) {
|
|
params.AuthToken = hp.authToken
|
|
params.OwnerAccountID = hp.ownerAccountID
|
|
reqBody, err := json.Marshal(params)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not marshal request body: %w", err)
|
|
}
|
|
|
|
url := fmt.Sprintf(endpoint, hp.baseURL, service, method)
|
|
resp, err := http.Post(url, "application/json", bytes.NewBuffer(reqBody))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not carry out request: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode >= 400 {
|
|
return nil, fmt.Errorf("error occurred: %s", resp.Status)
|
|
}
|
|
|
|
bodyBytes, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not read response body: %w", err)
|
|
}
|
|
|
|
respData := &response{}
|
|
if err := json.Unmarshal(bodyBytes, &respData); err != nil {
|
|
return nil, fmt.Errorf("could not unmarshal response body: %w", err)
|
|
}
|
|
if len(respData.Errors) > 0 && respData.Status == "error" {
|
|
return nil, fmt.Errorf("%+v", respData.Errors)
|
|
}
|
|
|
|
return respData.Response, nil
|
|
}
|
|
|
|
func (hp *hostingdeProvider) getAllZoneConfigs() ([]*zoneConfig, error) {
|
|
params := request{
|
|
Limit: 10000,
|
|
}
|
|
if hp.filterAccountID != "" {
|
|
params.Filter = &filter{
|
|
Field: "accountId",
|
|
Value: hp.filterAccountID,
|
|
}
|
|
}
|
|
|
|
resp, err := hp.get("dns", "zoneConfigsFind", params)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not get zones: %w", err)
|
|
}
|
|
|
|
zc := []*zoneConfig{}
|
|
if err := json.Unmarshal(resp.Data, &zc); err != nil {
|
|
return nil, fmt.Errorf("could not parse response: %w", err)
|
|
}
|
|
|
|
return zc, nil
|
|
}
|