1
0
mirror of https://github.com/StackExchange/dnscontrol.git synced 2024-05-11 05:55:12 +00:00

Provider support for DS records as children only (#765)

This functionality is required by the GCLOUD provider, which supports
recordsets of type DS but only for child records of the zone, to enable
further delegation. It does not support them at the apex of the zone (@)
because Google Cloud DNS is not itself a registrar which needs to model
this information.

A related change (14ff68b151, #760) was
previously introduced to enable DS support in Google, which broke
integration tests with this provider.

To cleanly support this, we introduce a new provider capability
CanUseDSForChildren and appropriate integration tests. Further, it is no
longer possible to verify a provider has the proper capabilities for a
zone simply by existence of particular records; we adapt the capability
checks to enable inspection of the individual recordsets where this is
required.

Closes #762
This commit is contained in:
Matthew Huxtable
2020-06-18 22:24:13 +01:00
committed by GitHub
parent 505062b628
commit ff8ce26cee
7 changed files with 201 additions and 37 deletions

View File

@ -447,31 +447,79 @@ func checkDuplicates(records []*models.RecordConfig) (errs []error) {
// We pull this out of checkProviderCapabilities() so that it's visible within
// the package elsewhere, so that our test suite can look at the list of
// capabilities we're checking and make sure that it's up-to-date.
var providerCapabilityChecks []pairTypeCapability
var providerCapabilityChecks = []pairTypeCapability{
// If a zone uses rType X, the provider must support capability Y.
//{"X", providers.Y},
capabilityCheck("ALIAS", providers.CanUseAlias),
capabilityCheck("AUTODNSSEC", providers.CanAutoDNSSEC),
capabilityCheck("CAA", providers.CanUseCAA),
capabilityCheck("NAPTR", providers.CanUseNAPTR),
capabilityCheck("PTR", providers.CanUsePTR),
capabilityCheck("R53_ALIAS", providers.CanUseRoute53Alias),
capabilityCheck("SSHFP", providers.CanUseSSHFP),
capabilityCheck("SRV", providers.CanUseSRV),
capabilityCheck("TLSA", providers.CanUseTLSA),
capabilityCheck("AZURE_ALIAS", providers.CanUseAzureAlias),
// DS needs special record-level checks
{
rType: "DS",
caps: []providers.Capability{providers.CanUseDS, providers.CanUseDSForChildren},
checkFunc: checkProviderDS,
},
}
type pairTypeCapability struct {
rType string
cap providers.Capability
// Capabilities the provider must implement if any records of type rType are found
// in the zonefile. This is a disjunction - implementing at least one of the listed
// capabilities is sufficient.
caps []providers.Capability
// checkFunc provides additional checks of each provider. This function should be
// called if records of type rType are found in the zonefile.
checkFunc func(pType string, _ models.Records) error
}
func init() {
providerCapabilityChecks = []pairTypeCapability{
// If a zone uses rType X, the provider must support capability Y.
//{"X", providers.Y},
{"ALIAS", providers.CanUseAlias},
{"AUTODNSSEC", providers.CanAutoDNSSEC},
{"CAA", providers.CanUseCAA},
{"DS", providers.CanUseDS},
{"NAPTR", providers.CanUseNAPTR},
{"PTR", providers.CanUsePTR},
{"R53_ALIAS", providers.CanUseRoute53Alias},
{"SSHFP", providers.CanUseSSHFP},
{"SRV", providers.CanUseSRV},
{"TLSA", providers.CanUseTLSA},
{"AZURE_ALIAS", providers.CanUseAzureAlias},
func capabilityCheck(rType string, caps ...providers.Capability) pairTypeCapability {
return pairTypeCapability{
rType: rType,
caps: caps,
}
}
func providerHasAtLeastOneCapability(pType string, caps ...providers.Capability) bool {
for _, cap := range caps {
if providers.ProviderHasCapability(pType, cap) {
return true
}
}
return false
}
func checkProviderDS(pType string, records models.Records) error {
switch {
case providers.ProviderHasCapability(pType, providers.CanUseDS):
// The provider can use DS records anywhere, including at the root
return nil
case !providers.ProviderHasCapability(pType, providers.CanUseDSForChildren):
// Provider has no support for DS records
return fmt.Errorf("provider %s uses DS records but does not support them", pType)
default:
// Provider supports DS records but not at the root
for _, record := range records {
if record.Type == "DS" && record.Name == "@" {
return fmt.Errorf(
"provider %s only supports child DS records, but zone had a record at the root (@)",
pType,
)
}
}
}
return nil
}
func checkProviderCapabilities(dc *models.DomainConfig) error {
// Check if the zone uses a capability that the provider doesn't
// support.
@ -496,9 +544,16 @@ func checkProviderCapabilities(dc *models.DomainConfig) error {
}
for _, provider := range dc.DNSProviderInstances {
// fmt.Printf(" (checking if %q can %q for domain %q)\n", provider.ProviderType, ty.rType, dc.Name)
if !providers.ProviderHasCapability(provider.ProviderType, ty.cap) {
if !providerHasAtLeastOneCapability(provider.ProviderType, ty.caps...) {
return fmt.Errorf("Domain %s uses %s records, but DNS provider type %s does not support them", dc.Name, ty.rType, provider.ProviderType)
}
if ty.checkFunc != nil {
checkErr := ty.checkFunc(provider.ProviderType, dc.Records)
if checkErr != nil {
return fmt.Errorf("while checking %s records in domain %s: %w", ty.rType, dc.Name, checkErr)
}
}
}
}
return nil