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

Support ALIAS records with Cloudflare (#89)

* simple facility for registering provider capabilities

* support for cloudflare. tests.

* js and docs

* docs

* generate
This commit is contained in:
Craig Peterson
2017-04-19 13:13:28 -06:00
committed by GitHub
parent ffb2ee7673
commit fb14cea91e
14 changed files with 265 additions and 89 deletions

View File

@ -0,0 +1,25 @@
---
name: ALIAS
parameters:
- name
- target
- modifiers...
---
ALIAS is a virtual record type that points a record at another record. It is analagous to a CNAME, but is usually resolved at request-time and served as an A record. Unlike CNAMEs, ALIAS records can be used at the zone apex (`@`)
Different providers handle ALIAS records differently, and many do not support it at all. Attempting to use ALIAS records with a DNS provider type that does not support them will result in an error.
The name should be the relative label for the domain.
Target should be a string representing the target. If it is a single label we will assume it is a relative name on the current domain. If it contains *any* dots, it should be a fully qualified domain name, ending with a `.`.
{% include startExample.html %}
{% highlight js %}
D("example.com", REGISTRAR, DnsProvider("CLOUDFLARE"),
ALIAS("@", "google.com."), // example.com -> google.com
);
{%endhighlight%}
{% include endExample.html %}

25
docs/alias.md Normal file
View File

@ -0,0 +1,25 @@
---
layout: default
---
# ALIAS records
ALIAS records are not widely standardized across DNS providers. Some (Route 53, DNSimple) have a native ALIAS record type. Others (Cloudflare) implement transparent CNAME flattening.
DNSControl adds an ALIAS record type, and leaves it up to the provider implementation to handle it.
A few notes:
1. A provider must "opt-in" to supporting ALIAS records. When registering a provider, you specify which capabilities you support. Here is an example of how the
cloudflare provider declares its support for aliases:
```
func init() {
providers.RegisterDomainServiceProviderType("CLOUDFLAREAPI", newCloudflare, providers.CanUseAlias)
}
```
2. If you try to use ALIAS records, **all** dns providers for the domain must support ALIAS records. We do not want to serve inconsistent records across providers.
3. CNAMEs at `@` are disallowed, but ALIAS is allowed.
4. Cloudflare does not have a native ALIAS type, but CNAMEs behave similarly. The Cloudflare provider "rewrites" ALIAS records to CNAME as it sees them. Other providers may not need this step.

View File

@ -23,3 +23,4 @@ Dnscontrol is a platform for seamlessly managing your dns configuration across a
- [Why CNAME/MX/NS targets require a trailing "dot"]({{site.github.url}}/why-the-dot)
- [Writing Providers]({{site.github.url}}/writing-providers)
- [ALIAS records in dnscontrol]({{site.github.url}}/alias)

View File

@ -97,6 +97,10 @@ func runTests(t *testing.T, prv providers.DNSServiceProvider, domainName string,
break
}
t.Run(fmt.Sprintf("%d: %s", i, tst.Desc), func(t *testing.T) {
if tst.SkipUnless != 0 && !providers.ProviderHasCabability(*providerToRun, tst.SkipUnless) {
t.Log("Skipping because provider does not support test features")
return
}
skipVal := false
if knownFailures[i] {
t.Log("SKIPPING VALIDATION FOR KNOWN FAILURE CASE")
@ -186,8 +190,9 @@ func TestDualProviders(t *testing.T) {
}
type TestCase struct {
Desc string
Records []*rec
Desc string
Records []*rec
SkipUnless providers.Capability
}
type rec models.RecordConfig
@ -200,6 +205,10 @@ func cname(name, target string) *rec {
return makeRec(name, target, "CNAME")
}
func alias(name, target string) *rec {
return makeRec(name, target, "ALIAS")
}
func ns(name, target string) *rec {
return makeRec(name, target, "NS")
}
@ -231,6 +240,11 @@ func tc(desc string, recs ...*rec) *TestCase {
}
}
func (tc *TestCase) IfHasCapability(c providers.Capability) *TestCase {
tc.SkipUnless = c
return tc
}
//ALWAYS ADD TO BOTTOM OF LIST. Order and indexes matter.
var tests = []*TestCase{
// A
@ -274,5 +288,12 @@ var tests = []*TestCase{
tc("Change to other name", mx("@", 5, "foo2.com."), mx("mail", 15, "foo3.com.")),
tc("Change Priority", mx("@", 7, "foo2.com."), mx("mail", 15, "foo3.com.")),
tc("IDN pre-punycoded", cname("xn--o-0gab", "xn--o-0gab.xn--o-0gab.")),
//ALIAS
tc("EMPTY"),
tc("ALIAS at root", alias("@", "foo.com.")).IfHasCapability(providers.CanUseAlias),
tc("change it", alias("@", "foo2.com.")).IfHasCapability(providers.CanUseAlias),
tc("ALIAS at subdomain", alias("test", "foo.com.")).IfHasCapability(providers.CanUseAlias),
//TODO: in validation, check that everything is given in unicode. This case hurts too much.
//tc("IDN pre-punycoded", cname("xn--o-0gab", "xn--o-0gab.xn--o-0gab.")),
}

View File

@ -1,7 +1,17 @@
{
"ACTIVEDIRECTORY_PS": {
"knownFailures": "17,18,19,25,26,27,28,29,30",
"domain": "$AD_DOMAIN",
"ADServer": "$AD_SERVER"
},
"BIND": {
"domain": "example.com"
},
"CLOUDFLAREAPI":{
"domain": "$CF_DOMAIN",
"apiuser": "$CF_USER",
"apikey": "$CF_KEY"
},
"DNSIMPLE": {
//16/17: no ns records managable. Not even for subdomains.
"knownFailures": "17,18",
@ -9,7 +19,7 @@
"token": "$DNSIMPLE_TOKEN",
"baseurl": "https://api.sandbox.dnsimple.com"
},
"GANDI":{
"GANDI": {
//5: gandi does not accept ttls less than 300
"knownFailures": "5",
"domain": "$GANDI_DOMAIN",
@ -25,10 +35,5 @@
"domain": "$R53_DOMAIN",
"KeyId": "$R53_KEY_ID",
"SecretKey": "$R53_KEY"
},
"ACTIVEDIRECTORY_PS":{
"knownFailures": "17,18,19,25,26,27,28,29,30",
"domain": "$AD_DOMAIN",
"ADServer": "$AD_SERVER"
}
}

View File

@ -1,4 +1,2 @@
$TTL 300
@ IN SOA DEFAULT_NOT_SET. DEFAULT_NOT_SET. 2017032394 3600 600 604800 1440
IN MX 7 foo2.com.
mail IN MX 15 foo3.com.
@ IN SOA DEFAULT_NOT_SET. DEFAULT_NOT_SET. 2017041717 3600 600 604800 1440

View File

@ -123,6 +123,15 @@ function AAAA(name, ip) {
}
}
// ALIAS(name,target, recordModifiers...)
function ALIAS(name, target) {
var mods = getModifiers(arguments,2)
return function(d) {
addRecord(d,"ALIAS",name,target,mods)
}
}
// CNAME(name,target, recordModifiers...)
function CNAME(name, target) {
var mods = getModifiers(arguments,2)

View File

@ -30,39 +30,40 @@ func TestParsedFiles(t *testing.T) {
if filepath.Ext(f.Name()) != ".js" || !unicode.IsNumber(rune(f.Name()[0])) {
continue
}
t.Log(f.Name(), "------")
content, err := ioutil.ReadFile(filepath.Join(testDir, f.Name()))
if err != nil {
t.Fatal(err)
}
conf, err := ExecuteJavascript(string(content), true)
if err != nil {
t.Fatal(err)
}
actualJson, err := json.MarshalIndent(conf, "", " ")
if err != nil {
t.Fatal(err)
}
expectedFile := filepath.Join(testDir, f.Name()[:len(f.Name())-3]+".json")
expectedData, err := ioutil.ReadFile(expectedFile)
if err != nil {
t.Fatal(err)
}
conf = &models.DNSConfig{}
err = json.Unmarshal(expectedData, conf)
if err != nil {
t.Fatal(err)
}
expectedJson, err := json.MarshalIndent(conf, "", " ")
if err != nil {
t.Fatal(err)
}
if string(expectedJson) != string(actualJson) {
t.Error("Expected and actual json don't match")
t.Log("Expected:", string(expectedJson))
t.Log("Actual:", string(actualJson))
t.FailNow()
}
t.Run(f.Name(), func(t *testing.T) {
content, err := ioutil.ReadFile(filepath.Join(testDir, f.Name()))
if err != nil {
t.Fatal(err)
}
conf, err := ExecuteJavascript(string(content), true)
if err != nil {
t.Fatal(err)
}
actualJSON, err := json.MarshalIndent(conf, "", " ")
if err != nil {
t.Fatal(err)
}
expectedFile := filepath.Join(testDir, f.Name()[:len(f.Name())-3]+".json")
expectedData, err := ioutil.ReadFile(expectedFile)
if err != nil {
t.Fatal(err)
}
conf = &models.DNSConfig{}
//unmarshal and remarshal to not require manual formatting
err = json.Unmarshal(expectedData, conf)
if err != nil {
t.Fatal(err)
}
expectedJSON, err := json.MarshalIndent(conf, "", " ")
if err != nil {
t.Fatal(err)
}
if string(expectedJSON) != string(actualJSON) {
t.Error("Expected and actual json don't match")
t.Log("Expected:", string(expectedJSON))
t.Log("Actual:", string(actualJSON))
}
})
}
}

View File

@ -0,0 +1,3 @@
D("foo.com","none",
ALIAS("@","foo.com.")
);

View File

@ -0,0 +1,19 @@
{
"registrars": [],
"dns_providers": [],
"domains": [
{
"name": "foo.com",
"registrar": "none",
"dnsProviders": {},
"records": [
{
"type": "ALIAS",
"name": "@",
"target": "foo.com."
}
],
"keepunknown": false
}
]
}

View File

@ -190,48 +190,48 @@ var _escData = map[string]*_escFile{
"/helpers.js": {
local: "js/helpers.js",
size: 7563,
size: 7758,
modtime: 0,
compressed: `
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
`,
},

View File

@ -6,6 +6,7 @@ import (
"strings"
"github.com/StackExchange/dnscontrol/models"
"github.com/StackExchange/dnscontrol/providers"
"github.com/StackExchange/dnscontrol/transform"
"github.com/miekg/dns"
"github.com/miekg/dns/dnsutil"
@ -52,6 +53,7 @@ func validateRecordTypes(rec *models.RecordConfig, domain string) error {
"MX": true,
"TXT": true,
"NS": true,
"ALIAS": false,
}
if _, ok := validTypes[rec.Type]; !ok {
@ -112,6 +114,9 @@ func checkTargets(rec *models.RecordConfig, domain string) (errs []error) {
check(checkIPv6(target))
case "CNAME":
check(checkTarget(target))
if label == "@" {
check(fmt.Errorf("cannot create CNAME record for bare domain"))
}
case "MX":
check(checkTarget(target))
case "NS":
@ -119,6 +124,8 @@ func checkTargets(rec *models.RecordConfig, domain string) (errs []error) {
if label == "@" {
check(fmt.Errorf("cannot create NS record for bare domain. Use NAMESERVER instead"))
}
case "ALIAS":
check(checkTarget(target))
case "TXT", "IMPORT_TRANSFORM":
default:
errs = append(errs, fmt.Errorf("Unimplemented record type (%v) domain=%v name=%v",
@ -260,9 +267,20 @@ func NormalizeAndValidateConfig(config *models.DNSConfig) (errs []error) {
errs = append(errs, err)
}
}
//Check that CNAMES don't have to co-exist with any other records
for _, d := range config.Domains {
errs = append(errs, checkCNAMEs(d)...)
}
//Check that if any aliases are used in a domain, every provider for that domain supports them
for _, d := range config.Domains {
err := checkALIASes(d, config.DNSProviders)
if err != nil {
errs = append(errs, nil)
}
}
return errs
}
@ -284,6 +302,30 @@ func checkCNAMEs(dc *models.DomainConfig) (errs []error) {
return
}
func checkALIASes(dc *models.DomainConfig, pList []*models.DNSProviderConfig) error {
hasAlias := false
for _, r := range dc.Records {
if r.Type == "ALIAS" {
hasAlias = true
break
}
}
if !hasAlias {
return nil
}
for pName := range dc.DNSProviders {
for _, p := range pList {
if p.Name == pName {
if !providers.ProviderHasCabability(p.Type, providers.CanUseAlias) {
return fmt.Errorf("Domain %s uses ALIAS records, but DNS provider type %s does not support them", dc.Name, p.Type)
}
break
}
}
}
return nil
}
func applyRecordTransforms(domain *models.DomainConfig) error {
for _, rec := range domain.Records {
if rec.Type != "A" {

View File

@ -81,7 +81,6 @@ func (c *CloudflareApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models
if err != nil {
return nil, err
}
//for _, rec := range records {
for i := len(records) - 1; i >= 0; i-- {
rec := records[i]
// Delete ignore labels
@ -91,9 +90,11 @@ func (c *CloudflareApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models
}
}
for _, rec := range dc.Records {
if rec.Type == "ALIAS" {
rec.Type = "CNAME"
}
if labelMatches(rec.Name, c.ignoredLabels) {
log.Fatalf("FATAL: dnsconfig contains label that matches ignored_labels: %#v is in %v)\n", rec.Name, c.ignoredLabels)
// Since we log.Fatalf, we don't need to be clean here.
}
}
checkNSModifications(dc)
@ -166,9 +167,15 @@ func (c *CloudflareApi) preprocessConfig(dc *models.DomainConfig) error {
// A and CNAMEs: Validate. If null, set to default.
// else: Make sure it wasn't set. Set to default.
for _, rec := range dc.Records {
if rec.Metadata == nil {
rec.Metadata = map[string]string{}
}
if rec.TTL == 0 || rec.TTL == 300 {
rec.TTL = 1
}
if rec.TTL != 1 && rec.TTL < 120 {
rec.TTL = 120
}
if rec.Type != "A" && rec.Type != "CNAME" && rec.Type != "AAAA" {
if rec.Metadata[metaProxy] != "" {
return fmt.Errorf("cloudflare_proxy set on %v record: %#v cloudflare_proxy=%#v", rec.Type, rec.Name, rec.Metadata[metaProxy])
@ -243,7 +250,7 @@ func newCloudflare(m map[string]string, metadata json.RawMessage) (providers.DNS
}
func init() {
providers.RegisterDomainServiceProviderType("CLOUDFLAREAPI", newCloudflare)
providers.RegisterDomainServiceProviderType("CLOUDFLAREAPI", newCloudflare, providers.CanUseAlias)
}
// Used on the "existing" records.

View File

@ -34,6 +34,21 @@ var registrarTypes = map[string]RegistrarInitializer{}
type DspInitializer func(map[string]string, json.RawMessage) (DNSServiceProvider, error)
var dspTypes = map[string]DspInitializer{}
var dspCapabilities = map[string]Capability{}
//Capability is a bitmasked set of "features" that a provider supports. Only use constants from this package.
type Capability uint32
const (
// CanUseAlias indicates the provider support ALIAS records (or flattened CNAMES). Up to the provider to translate them to the appropriate record type.
CanUseAlias Capability = 1 << iota
// CanUsePTR indicates the provider can handle PTR records
CanUsePTR
)
func ProviderHasCabability(pType string, cap Capability) bool {
return dspCapabilities[pType]&cap != 0
}
//RegisterRegistrarType adds a registrar type to the registry by providing a suitable initialization function.
func RegisterRegistrarType(name string, init RegistrarInitializer) {
@ -44,11 +59,16 @@ func RegisterRegistrarType(name string, init RegistrarInitializer) {
}
//RegisterDomainServiceProviderType adds a dsp to the registry with the given initialization function.
func RegisterDomainServiceProviderType(name string, init DspInitializer) {
func RegisterDomainServiceProviderType(name string, init DspInitializer, caps ...Capability) {
if _, ok := dspTypes[name]; ok {
log.Fatalf("Cannot register registrar type %s multiple times", name)
}
var abilities Capability
for _, c := range caps {
abilities |= c
}
dspTypes[name] = init
dspCapabilities[name] = abilities
}
func createRegistrar(rType string, config map[string]string) (Registrar, error) {