mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2025-11-08 07:20:49 +08:00
Implement DS record support for ClouDNS (#1018)
* Add PTR support for ClouDNS * Implement PTR Support for CLouDNS * implemnent DS Record for ClouDNS * implement DS record for clouDNS * pull request review * note that SshFpAlgorithm and DsAlgorithm both use json field algorithm * primitive rate limit and fix order of NS/DS-entries * codefixes Co-authored-by: IT-Sumpfling <it-sumpfling@maxit-con.de> Co-authored-by: bentaybi jamal <jamal@pfalzcloud.de> Co-authored-by: Tom Limoncelli <tlimoncelli@stackoverflow.com>
This commit is contained in:
parent
20a726df27
commit
d7f40ed680
4 changed files with 99 additions and 4 deletions
|
|
@ -23,7 +23,11 @@ Current version of provider doesn't support `sub-auth-user`.
|
|||
|
||||
## Records
|
||||
|
||||
ClouDNS does not supprt DS Record.
|
||||
ClouDNS does support DS Record on subdomains (not the apex domain itself).
|
||||
|
||||
ClouDNS requires NS records exist for any DS records. No other records for
|
||||
the same label may exist (A, MX, TXT, etc.). If DNSControl is adding NS and
|
||||
DS records in the same update, the NS records will be inserted first.
|
||||
|
||||
## Metadata
|
||||
This provider does not recognize any special metadata fields unique to ClouDNS.
|
||||
|
|
|
|||
|
|
@ -240,6 +240,7 @@ func runTests(t *testing.T, prv providers.DNSServiceProvider, domainName string,
|
|||
}
|
||||
|
||||
// Run the tests.
|
||||
|
||||
for _, tst := range group.tests {
|
||||
makeChanges(t, prv, dc, tst, fmt.Sprintf("%02d:%s", gIdx, group.Desc), true, origConfig)
|
||||
if t.Failed() {
|
||||
|
|
@ -980,6 +981,7 @@ func makeTests(t *testing.T) []*TestGroup {
|
|||
|
||||
testgroup("DS (children only)",
|
||||
requires(providers.CanUseDSForChildren),
|
||||
not("CLOUDNS"),
|
||||
// Use a valid digest value here, because GCLOUD (which implements this capability) verifies
|
||||
// the value passed in is a valid digest. RFC 4034, s5.1.4 specifies SHA1 as the only digest
|
||||
// algo at present, i.e. only hexadecimal values currently usable.
|
||||
|
|
@ -997,6 +999,52 @@ func makeTests(t *testing.T) []*TestGroup {
|
|||
),
|
||||
),
|
||||
|
||||
testgroup("DS (children only) CLOUDNS",
|
||||
requires(providers.CanUseDSForChildren),
|
||||
only("CLOUDNS"),
|
||||
// Use a valid digest value here, because GCLOUD (which implements this capability) verifies
|
||||
// the value passed in is a valid digest. RFC 4034, s5.1.4 specifies SHA1 as the only digest
|
||||
// algo at present, i.e. only hexadecimal values currently usable.
|
||||
// Cloudns requires NS Record before creating DS Record.
|
||||
tc("create DS",
|
||||
// we test that provider correctly handles creating NS first by reversing the entries here
|
||||
ds("child", 35632, 13, 1, "1E07663FF507A40874B8605463DD41DE482079D6"),
|
||||
ns("child", "ns101.cloudns.net."),
|
||||
),
|
||||
tc("modify field 1",
|
||||
ds("child", 2075, 13, 1, "2706D12E256C8FDD9BFB45EFB25FE537E21A82F6"),
|
||||
ns("child", "ns101.cloudns.net."),
|
||||
),
|
||||
tc("modify field 3",
|
||||
ds("child", 2075, 13, 2, "3F7A1EAC8C813A0BEBD0C3B8AAB387E31945EA0CD5E1D84A2E8E27674566C156"),
|
||||
ns("child", "ns101.cloudns.net."),
|
||||
),
|
||||
tc("modify field 2+3",
|
||||
ds("child", 2159, 1, 4, "F50BEFEA333EE2901D72D31A08E1A3CD3F7E943FF4B38CF7C8AD92807F5302F76FB0B419182C0F47FFC71CBCB6EF4BD4"),
|
||||
ns("child", "ns101.cloudns.net."),
|
||||
),
|
||||
tc("modify field 2",
|
||||
ds("child", 63909, 3, 4, "EEC7FA02E6788DA889B2CE41D43D92F948AB126EDCF83B7037E73CE9531C8E7E45653ABBAA76C2D6E42F98316EDE599B"),
|
||||
ns("child", "ns101.cloudns.net."),
|
||||
),
|
||||
//tc("modify field 2", ds("child", 65535, 254, 4, "0123456789ABCDEF")),
|
||||
tc("delete 1, create 1",
|
||||
ds("another-child", 35632, 13, 4, "F5F32ABCA6B01AA7A9963012F90B7C8523A1D946185A3AD70B67F3C9F18E7312FA9DD6AB2F7D8382F789213DB173D429"),
|
||||
ns("another-child", "ns101.cloudns.net."),
|
||||
),
|
||||
tc("add 2 more DS",
|
||||
ds("another-child", 35632, 13, 4, "F5F32ABCA6B01AA7A9963012F90B7C8523A1D946185A3AD70B67F3C9F18E7312FA9DD6AB2F7D8382F789213DB173D429"),
|
||||
ds("another-child", 2159, 1, 4, "F50BEFEA333EE2901D72D31A08E1A3CD3F7E943FF4B38CF7C8AD92807F5302F76FB0B419182C0F47FFC71CBCB6EF4BD4"),
|
||||
ds("another-child", 63909, 3, 4, "EEC7FA02E6788DA889B2CE41D43D92F948AB126EDCF83B7037E73CE9531C8E7E45653ABBAA76C2D6E42F98316EDE599B"),
|
||||
ns("another-child", "ns101.cloudns.net."),
|
||||
),
|
||||
// in CLouDNS we must delete DS Record before deleting NS record
|
||||
// should no longer be necessary, provider should handle order correctly
|
||||
//tc("delete all DS",
|
||||
// ns("another-child", "ns101.cloudns.net."),
|
||||
//),
|
||||
),
|
||||
|
||||
//
|
||||
// Pseudo rtypes:
|
||||
//
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Api layer for CloDNS
|
||||
|
|
@ -62,6 +63,10 @@ type domainRecord struct {
|
|||
TlsaMatchingType string `json:"tlsa_matching_type,omitempty"`
|
||||
SshfpAlgorithm string `json:"algorithm,omitempty"`
|
||||
SshfpFingerprint string `json:"fp_type,omitempty"`
|
||||
DsKeyTag string `json:"key_tag,omitempty"`
|
||||
DsAlgorithm string `json:"dsalgorithm,omitempty"`
|
||||
DsDigestType string `json:"digest_type,omitempty"`
|
||||
DsDigest string `json:"dsdigest,omitempty"`
|
||||
}
|
||||
|
||||
type recordResponse map[string]domainRecord
|
||||
|
|
@ -143,7 +148,7 @@ func (c *cloudnsProvider) createDomain(domain string) error {
|
|||
|
||||
func (c *cloudnsProvider) createRecord(domainID string, rec requestParams) error {
|
||||
rec["domain-name"] = domainID
|
||||
if _, err := c.get("/dns/add-record.json", rec); err != nil {
|
||||
if _, err := c.get("/dns/add-record.json", rec); err != nil { // here we add record
|
||||
return fmt.Errorf("failed create record (ClouDNS): %s", err)
|
||||
}
|
||||
return nil
|
||||
|
|
@ -204,6 +209,9 @@ func (c *cloudnsProvider) get(endpoint string, params requestParams) ([]byte, er
|
|||
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
// ClouDNS has a rate limit (not documented) of 10 request/second
|
||||
// so we do a very primitive rate-limiting here - delay every request for 100ms - so max. 10 requests/second ...
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ var features = providers.DocumentationNotes{
|
|||
providers.CanUseTLSA: providers.Can(),
|
||||
providers.CanUsePTR: providers.Can(),
|
||||
providers.CanGetZones: providers.Can(),
|
||||
providers.CanUseDSForChildren: providers.Can(),
|
||||
//providers.CanUseDS: providers.Can(),
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
@ -111,9 +113,17 @@ func (c *cloudnsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode
|
|||
return c.deleteRecord(domainID, id)
|
||||
},
|
||||
}
|
||||
corrections = append(corrections, corr)
|
||||
// at ClouDNS, we MUST have a NS for a DS
|
||||
// So, when deleting, we must delete the DS first, otherwise deleting the NS throws an error
|
||||
if m.Existing.Type == "DS" {
|
||||
// type DS is prepended - so executed first
|
||||
corrections = append([]*models.Correction{corr}, corrections...)
|
||||
} else {
|
||||
corrections = append(corrections, corr)
|
||||
}
|
||||
}
|
||||
|
||||
var createCorrections []*models.Correction
|
||||
for _, m := range create {
|
||||
req, err := toReq(m.Desired)
|
||||
if err != nil {
|
||||
|
|
@ -126,8 +136,17 @@ func (c *cloudnsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode
|
|||
return c.createRecord(domainID, req)
|
||||
},
|
||||
}
|
||||
corrections = append(corrections, corr)
|
||||
// at ClouDNS, we MUST have a NS for a DS
|
||||
// So, when creating, we must create the NS first, otherwise creating the DS throws an error
|
||||
if m.Desired.Type == "NS" {
|
||||
// type NS is prepended - so executed first
|
||||
createCorrections = append([]*models.Correction{corr}, createCorrections...)
|
||||
} else {
|
||||
createCorrections = append(createCorrections, corr)
|
||||
}
|
||||
}
|
||||
corrections = append(corrections, createCorrections...)
|
||||
|
||||
for _, m := range modify {
|
||||
id := m.Existing.Original.(*domainRecord).ID
|
||||
req, err := toReq(m.Desired)
|
||||
|
|
@ -172,6 +191,7 @@ func (c *cloudnsProvider) EnsureDomainExists(domain string) error {
|
|||
return c.createDomain(domain)
|
||||
}
|
||||
|
||||
//parses the ClouDNS format into our standard RecordConfig
|
||||
func toRc(domain string, r *domainRecord) *models.RecordConfig {
|
||||
|
||||
ttl, _ := strconv.ParseUint(r.TTL, 10, 32)
|
||||
|
|
@ -214,6 +234,15 @@ func toRc(domain string, r *domainRecord) *models.RecordConfig {
|
|||
sshfpFingerprint, _ := strconv.ParseUint(r.SshfpFingerprint, 10, 32)
|
||||
rc.SshfpFingerprint = uint8(sshfpFingerprint)
|
||||
rc.SetTarget(r.Target)
|
||||
case "DS":
|
||||
dsKeyTag, _ := strconv.ParseUint(r.DsKeyTag, 10, 32)
|
||||
rc.DsKeyTag = uint16(dsKeyTag)
|
||||
dsAlgorithm, _ := strconv.ParseUint(r.SshfpAlgorithm, 10, 32) // SshFpAlgorithm and DsAlgorithm both use json field "algorithm"
|
||||
rc.DsAlgorithm = uint8(dsAlgorithm)
|
||||
dsDigestType, _ := strconv.ParseUint(r.DsDigestType, 10, 32)
|
||||
rc.DsDigestType = uint8(dsDigestType)
|
||||
rc.DsDigest = r.Target
|
||||
rc.SetTarget(r.Target)
|
||||
default:
|
||||
rc.SetTarget(r.Target)
|
||||
}
|
||||
|
|
@ -221,6 +250,7 @@ func toRc(domain string, r *domainRecord) *models.RecordConfig {
|
|||
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{
|
||||
"record-type": rc.Type,
|
||||
|
|
@ -254,6 +284,11 @@ func toReq(rc *models.RecordConfig) (requestParams, error) {
|
|||
case "SSHFP":
|
||||
req["algorithm"] = strconv.Itoa(int(rc.SshfpAlgorithm))
|
||||
req["fptype"] = strconv.Itoa(int(rc.SshfpFingerprint))
|
||||
case "DS":
|
||||
req["key-tag"] = strconv.Itoa(int(rc.DsKeyTag))
|
||||
req["algorithm"] = strconv.Itoa(int(rc.DsAlgorithm))
|
||||
req["digest-type"] = strconv.Itoa(int(rc.DsDigestType))
|
||||
req["record"] = rc.DsDigest
|
||||
default:
|
||||
return nil, fmt.Errorf("ClouDNS.toReq rtype %q unimplemented", rc.Type)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue